source: contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph.php @ 3594

Revision 3594, 160.2 KB checked in by afernandes, 13 years ago (diff)

Ticket #1416 - Disponibilizado o módulo ProjectManager? para a comunidade

  • Property svn:executable set to *
Line 
1<?php
2/*=======================================================================
3// File:        JPGRAPH.PHP
4// Description: PHP4 Graph Plotting library. Base module.
5// Created:     2001-01-08
6// Author:      Johan Persson (johanp@aditus.nu)
7// Ver:         $Id: jpgraph.php 21908 2006-06-21 18:30:15Z ralfbecker $
8//
9// License:     This code is released under GPL 2.0
10// Copyright (C) 2001 Johan Persson
11//========================================================================
12*/
13
14// Get all directory paths
15include ( "jpgraph_dir.php" );
16
17// What group should the cached file belong to
18// (Set to "" will give the default group for the "PHP-user")
19// Please note that the Apache user must be a member of the
20// specified group since otherwise it is impossible for Apache
21// to set the specified group.
22DEFINE("CACHE_FILE_GROUP","");
23
24// What permissions should the cached file have
25// (Set to "" will give the default persmissions for the "PHP-user")
26DEFINE("CACHE_FILE_MOD",0664);
27
28// Should we try to find an image in the cache before generating it?
29// Set this define to false to bypass the reading of the cache and always
30// regenerate the image. Note that even if reading the cache is
31// disabled the cached will still be updated with the newly generated
32// image. Set also "USE_CACHE" below.
33DEFINE("READ_CACHE",false);
34
35// Should the cache be used at all? By setting this to false no
36// files will be generated in the cache directory. 
37// The difference from READ_CACHE being that setting READ_CACHE to
38// false will still create the image in the cache directory
39// just not use it. By setting USE_CACHE=false no files will even
40// be generated in the cache directory.
41DEFINE("USE_CACHE",false);
42
43// If the color palette is full should JpGraph try to allocate
44// the closest match? If you plan on using background image or
45// gradient fills it might be a good idea to enable this.
46// If not you will otherwise get an error saying that the color palette is
47// exhausted. The drawback of using approximations is that the colors
48// might not be exactly what you specified.
49// Note1: This does only apply to paletted images, not truecolor
50// images since they don't have the limitations of maximum number
51// of colors.
52DEFINE("USE_APPROX_COLORS",true);
53
54// Should usage of deprecated functions and parameters give a fatal error?
55// (Useful to check if code is future proof.)
56DEFINE("ERR_DEPRECATED",true);
57
58// Should the time taken to generate each picture be branded to the lower
59// left in corner in each generated image? Useful for performace measurements
60// generating graphs
61DEFINE("BRAND_TIMING",false);
62
63// What format should be used for the timing string?
64DEFINE("BRAND_TIME_FORMAT","Generated in: %01.3fs");
65
66// Decide if we should use the bresenham circle algorithm or the
67// built in Arc(). Bresenham gives better visual apperance of circles
68// but is more CPU intensive and slower then the built in Arc() function
69// in GD. Turned off by default for speed
70DEFINE("USE_BRESENHAM",false);
71
72// Deafult graphic format set to "auto" which will automtically
73// choose the best available format in the order png,gif,jpg
74// (The supported format depends on what your PHP installation supports)
75DEFINE("DEFAULT_GFORMAT","auto");
76
77// Special unicode language support
78DEFINE("LANGUAGE_CYRILLIC",false);
79
80// Enable some extra debug information to be shown.
81// (Should only be used if your first name is Johan)
82DEFINE("JPG_DEBUG",false);
83
84// Should the image be a truecolor image?
85// Note 1: Can only be used with GD 2.0.2 and above.
86// Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use
87// trucolor. Truecolor support is to be considered alpha since GD 2.x
88// is still not considered stable (especially on Win32).
89// Note 1: MUST be enabled to get background images working with GD2
90// Note 2: If enabled then truetype fonts will look very ugly
91// => You can't have both background images and truetype fonts in the same
92// image until these bugs has been fixed in GD 2.01 
93DEFINE('USE_TRUECOLOR',true);
94
95//------------------------------------------------------------------
96// Constants which are used as parameters for the method calls
97//------------------------------------------------------------------
98
99// TTF Font families
100DEFINE("FF_COURIER",10);
101DEFINE("FF_VERDANA",11);
102DEFINE("FF_TIMES",12);
103DEFINE("FF_HANDWRT",13);
104DEFINE("FF_COMIC",14);
105DEFINE("FF_ARIAL",15);
106DEFINE("FF_BOOK",16);
107DEFINE("FF_VERA",17);
108
109// TTF Font styles
110DEFINE("FS_NORMAL",1);
111DEFINE("FS_BOLD",2);
112DEFINE("FS_ITALIC",3);
113DEFINE("FS_BOLDIT",4);
114
115//Definitions for internal font, new style
116DEFINE("FF_FONT0",1);
117DEFINE("FF_FONT1",2);
118DEFINE("FF_FONT2",4);
119
120//Definitions for internal font, old style
121// (Only defined here to be able to generate an error mesage
122// when used)
123DEFINE("FONT0",99);                     // Deprecated from 1.2
124DEFINE("FONT1",98);                     // Deprecated from 1.2
125DEFINE("FONT1_BOLD",97);        // Deprecated from 1.2
126DEFINE("FONT2",96);                     // Deprecated from 1.2
127DEFINE("FONT2_BOLD",95);        // Deprecated from 1.2
128
129// Tick density
130DEFINE("TICKD_DENSE",1);
131DEFINE("TICKD_NORMAL",2);
132DEFINE("TICKD_SPARSE",3);
133DEFINE("TICKD_VERYSPARSE",4);
134
135// Side for ticks and labels.
136DEFINE("SIDE_LEFT",-1);
137DEFINE("SIDE_RIGHT",1);
138DEFINE("SIDE_DOWN",-1);
139DEFINE("SIDE_UP",1);
140
141// Legend type stacked vertical or horizontal
142DEFINE("LEGEND_VERT",0);
143DEFINE("LEGEND_HOR",1);
144
145// Mark types for plot marks
146DEFINE("MARK_SQUARE",1);
147DEFINE("MARK_UTRIANGLE",2);
148DEFINE("MARK_DTRIANGLE",3);
149DEFINE("MARK_DIAMOND",4);
150DEFINE("MARK_CIRCLE",5);
151DEFINE("MARK_FILLEDCIRCLE",6);
152DEFINE("MARK_CROSS",7);
153DEFINE("MARK_STAR",8);
154DEFINE("MARK_X",9);
155
156// Styles for gradient color fill
157DEFINE("GRAD_VER",1);
158DEFINE("GRAD_HOR",2);
159DEFINE("GRAD_MIDHOR",3);
160DEFINE("GRAD_MIDVER",4);
161DEFINE("GRAD_CENTER",5);
162DEFINE("GRAD_WIDE_MIDVER",6);
163DEFINE("GRAD_WIDE_MIDHOR",7);
164
165// Inline defines
166DEFINE("INLINE_YES",1);
167DEFINE("INLINE_NO",0);
168
169// Format for background images
170DEFINE("BGIMG_FILLPLOT",1);
171DEFINE("BGIMG_FILLFRAME",2);
172DEFINE("BGIMG_COPY",3);
173DEFINE("BGIMG_CENTER",4);
174
175// Depth of objects
176DEFINE("DEPTH_BACK",0);
177DEFINE("DEPTH_FRONT",1);
178
179// Direction
180DEFINE("VERTICAL",1);
181DEFINE("HORIZONTAL",0);
182
183// Constants for types of static bands in plot area
184DEFINE("BAND_RDIAG",1); // Right diagonal lines
185DEFINE("BAND_LDIAG",2); // Left diagonal lines
186DEFINE("BAND_SOLID",3); // Solid one color
187DEFINE("BAND_LVERT",4); // Vertical lines
188DEFINE("BAND_LHOR",5);  // Horizontal lines
189DEFINE("BAND_VLINE",4); // Vertical lines
190DEFINE("BAND_HLINE",5);  // Horizontal lines
191DEFINE("BAND_3DPLANE",6);  // "3D" Plane
192DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
193DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
194
195
196
197//
198// First of all set up a default error handler
199//
200
201//=============================================================
202// The default trivial error handler.
203// A production quality error handler should generate an image
204// containing the text of the error.
205//=============================================================
206class JpGraphErrObject {
207    function JpGraphErrObject() {
208        // Empty. Reserved for future use
209    }
210
211    // If aHalt is true then execution can't continue. Typical used for
212    // fatal errors
213    function Raise($aMsg,$aHalt=true) {
214        if( $aHalt )
215            die($aMsg);
216        else
217            echo $aMsg."<p>";
218    }
219}
220
221//
222// A wrapper class that is used to access the specified error object
223// (to hide the global error parameter and avoid having a GLOBAL directive
224// in all methods.
225//
226class JpGraphError {
227    function Install($aErrObject) {
228        GLOBAL $__jpg_err;
229        $__jpg_err = $aErrObject;
230    }
231    function Raise($aMsg,$aHalt=true){
232        GLOBAL $__jpg_err;
233        $tmp = new $__jpg_err;
234        $tmp->Raise($aMsg,$aHalt);
235    }
236}
237
238//
239// ... and install the default error handler
240//
241JpGraphError::Install("JpGraphErrObject");
242
243//
244//Check if there were any warnings, perhaps some wrong includes by the
245//user
246//
247if( isset($GLOBALS['php_errormsg']) ) {
248    JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
249}
250
251//
252// Check what version of the GD library is being used
253//
254if(function_exists('imagecopyresampled') ) {
255    $gd2 = true;
256    $copyfunc = "imagecopyresampled";
257} elseif(function_exists('imagecopyresized')) {
258    $copyfunc = "imagecopyresized";
259    $gd2 = false;
260}
261else {
262    JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP installation does not
263        have the required GD library.
264        Please see the PHP documentation on how to install and enable the GD library.");
265}
266
267
268// Usefull mathematical function
269function sign($a) {if( $a>=0) return 1; else return -1;}
270
271// Utility function to generate an image name based on the filename we
272// are running from AND assuming we use auto detection of graphic format
273// (top level), i.e it is safe to call this function
274// from a script that uses JpGraph
275function GenImgName() {
276    $supported = imagetypes();
277    if( $supported & IMG_PNG )
278        $img_format="png";
279    elseif( $supported & IMG_GIF )
280        $img_format="gif";
281    elseif( $supported & IMG_JPG )
282        $img_format="jpeg";
283    if( !isset($_SERVER['PHP_SELF']) )
284        JpGraphError::Raise("<b>JpGraph Error:</b> Can't access PHP_SELF, PHP global variable. You can't run PHP from command line
285                if you want to use the 'auto' naming of cache or image files.");
286    $fname=basename($_SERVER['PHP_SELF']);
287    // Replace the ".php" extension with the image format extension
288    return substr($fname,0,strlen($fname)-4).".".$img_format;
289}
290
291
292class LanguageConv {
293
294//  Translate iso encoding to unicode
295    function iso2uni ($isoline){
296        for ($i=0; $i < strlen($isoline); $i++){
297            $thischar=substr($isoline,$i,1);
298            $charcode=ord($thischar);
299            $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
300        }
301        return $uniline;
302    }
303
304    function ToCyrillic($aTxt) {
305        $koistring = $aTxt;
306        $isostring = convert_cyr_string($koistring, "k", "i");
307        $unistring = LanguageConv::iso2uni($isostring);
308        $this->t = $unistring;
309        return $aTxt;
310    }
311}
312
313//===================================================
314// CLASS JpgTimer
315// Description: General timing utility class to handle
316// timne measurement of generating graphs. Multiple
317// timers can be started by pushing new on a stack.
318//===================================================
319class JpgTimer {
320    var $start;
321    var $idx;   
322//---------------
323// CONSTRUCTOR
324    function JpgTimer() {
325        $this->idx=0;
326    }
327
328//---------------
329// PUBLIC METHODS       
330
331    // Push a new timer start on stack
332    function Push() {
333        list($ms,$s)=explode(" ",microtime()); 
334        $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;   
335    }
336
337    // Pop the latest timer start and return the diff with the
338    // current time
339    function Pop() {
340        assert($this->idx>0);
341        list($ms,$s)=explode(" ",microtime()); 
342        $etime=floor($ms*1000) + (1000*$s);
343        $this->idx--;
344        return $etime-$this->start[$this->idx];
345    }
346} // Class
347
348
349//===================================================
350// CLASS Graph
351// Description: Main class to handle graphs
352//===================================================
353class Graph {
354    var $cache=null;            // Cache object (singleton)
355    var $img=null;                      // Img object (singleton)
356    var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
357    var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
358    var $xscale=null;           // X Scale object (could be instance of LinearScale or LogScale
359    var $yscale=null,$y2scale=null;
360    var $cache_name;            // File name to be used for the current graph in the cache directory
361    var $xgrid=null;            // X Grid object (linear or logarithmic)
362    var $ygrid=null,$y2grid=null; //dito for Y
363    var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;       // Frame around graph
364    var $boxed=false, $box_color=array(0,0,0), $box_weight=1;           // Box around plot area
365    var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);       // Shadow for graph
366    var $xaxis=null;            // X-axis (instane of Axis class)
367    var $yaxis=null, $y2axis=null;      // Y axis (instance of Axis class)
368    var $margin_color=array(198,198,198);       // Margin coor of graph
369    var $plotarea_color=array(255,255,255);     // Plot area color
370    var $title,$subtitle;       // Title and subtitle text object
371    var $axtype="linlin";       // Type of axis
372    var $xtick_factor;  // Factot to determine the maximum number of ticks depending on the plot with
373    var $texts=null;            // Text object to ge shown in the graph
374    var $lines=null;
375    var $bands=null;
376    var $text_scale_off=0;      // Text scale offset in world coordinates
377    var $background_image="",$background_image_type=-1,$background_image_format="png";
378    var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
379    var $image_bright=0, $image_contr=0, $image_sat=0;
380    var $inline;
381    var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
382    var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
383//---------------
384// CONSTRUCTOR
385
386    // aWIdth           Width in pixels of image
387    // aHeight          Height in pixels of image
388    // aCachedName      Name for image file in cache directory
389    //  aTimeOut        Timeout in minutes for image in cache
390    // aInline          If true the image is streamed back in the call to Stroke()
391    //             If false the image is just created in the cache
392    function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
393               
394        // If timing is used create a new timing object
395        if( BRAND_TIMING ) {
396            global $tim;
397            $tim = new JpgTimer();
398            $tim->Push();
399        }
400               
401        // Automtically generate the image file name based on the name of the script that
402        // generates the graph
403        if( $aCachedName=="auto" )
404            $aCachedName=GenImgName();
405                       
406        // Should the image be streamed back to the browser or only to the cache?
407        $this->inline=$aInline;
408               
409        $this->img              =  new RotImage($aWidth,$aHeight);
410        $this->cache    =  new ImgStreamCache($this->img);
411        $this->cache->SetTimeOut($aTimeOut);
412        $this->title    =  new Text();
413        $this->subtitle =  new Text();
414        $this->legend   =  new Legend();
415               
416        // If the cached version exist just read it directly from the
417        // cache, stream it back to browser and exit
418        if( $aCachedName!="" && READ_CACHE && $aInline )
419            if( $this->cache->GetAndStream($aCachedName) ) {
420                exit();
421            }
422                               
423        $this->cache_name = $aCachedName;
424        $this->SetTickDensity(); // Normal density
425    }
426//---------------
427// PUBLIC METHODS       
428
429    // Should the grid be in front or back of the plot?
430    function SetGridDepth($aDepth) {
431        $this->grid_depth=$aDepth;
432    }
433       
434    // Specify graph angle 0-360 degrees.
435    function SetAngle($aAngle) {
436        $this->img->SetAngle($aAngle);
437    }
438       
439    // Add a plot object to the graph
440    function Add(&$aPlot) {
441        if( $aPlot == null )
442            JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::Add() You tried to add a null plot to the graph.");                               
443        $this->plots[] = &$aPlot;
444    }
445       
446    // Add plot to second Y-scale
447    function AddY2(&$aPlot) {
448        if( $aPlot == null )
449            JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddY2() You tried to add a null plot to the graph.");                             
450        $this->y2plots[] = &$aPlot;
451    }
452       
453    // Add text object to the graph
454    function AddText(&$aTxt) {
455        if( $aTxt == null )
456            JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddText() You tried to add a null text to the graph.");           
457        if( is_array($aTxt) ) {
458            for($i=0; $i<count($aTxt); ++$i )
459                $this->texts[]=&$aTxt[$i];
460        }
461        else
462            $this->texts[] = &$aTxt;
463    }
464       
465    // Add a line object (class PlotLine) to the graph
466    function AddLine(&$aLine) {
467        if( $aLine == null )
468            JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddLine() You tried to add a null line to the graph.");           
469        if( is_array($aLine) ) {
470            for($i=0; $i<count($aLine); ++$i )
471                $this->lines[]=&$aLine[$i];
472        }
473        else
474            $this->lines[] = &$aLine;
475    }
476
477    // Add vertical or horizontal band
478    function AddBand(&$aBand) {
479        if( $aBand == null )
480            JpGraphError::Raise("<b>JpGraph Error:</b> Graph::AddBand() You tried to add a null band to the graph.");
481        if( is_array($aBand) ) {
482            for($i=0; $i<count($aBand); ++$i )
483                $this->bands[] = &$aBand[$i];
484        }
485        else
486            $this->bands[] = &$aBand;
487    }
488
489       
490    // Specify a background image
491    function SetBackgroundImage($aFileName,$aBgType=BKIMG_FILLPLOT,$aImgFormat="png") {
492
493        if( $GLOBALS["gd2"] && !USE_TRUECOLOR ) {
494            JpGraphError::Raise("<b>JpGraph Error:</b>You are using GD 2.x and are
495trying to use a background images on a non truecolor image. <br>
496To use
497background images with GD 2.x you <b>must</b> enable truecolor by setting the
498USE_TRUECOLOR constant to TRUE. <br>
499<b>Note:</b> Due to a bug in GD 2.0.1
500using any truetype fonts with truecolor images will result in very
501poor quality fonts.");
502        }
503
504        $this->background_image = $aFileName;
505        $this->background_image_type=$aBgType;
506        $this->background_image_format=$aImgFormat;
507    }
508       
509    // Adjust brightness and constrast for background image
510    function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
511        $this->background_image_bright=$aBright;
512        $this->background_image_contr=$aContr;
513        $this->background_image_sat=$aSat;
514    }
515       
516    // Adjust brightness and constrast for image
517    function AdjImage($aBright,$aContr=0,$aSat=0) {
518        $this->image_bright=$aBright;
519        $this->image_contr=$aContr;
520        $this->image_sat=$aSat;
521    }
522       
523    // Set a frame around the plot area
524    function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
525        $this->boxed = $aDrawPlotFrame;
526        $this->box_weight = $aPlotFrameWeight;
527        $this->box_color = $aPlotFrameColor;
528    }
529       
530    // Specify color for the plotarea (not the margins)
531    function SetColor($aColor) {
532        $this->plotarea_color=$aColor;
533    }
534       
535    // Specify color for the margins (all areas outside the plotarea)
536    function SetMarginColor($aColor) {
537        $this->margin_color=$aColor;
538    }
539       
540    // Set a frame around the entire image
541    function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
542        $this->doframe = $aDrawImgFrame;
543        $this->frame_color = $aImgFrameColor;
544        $this->frame_weight = $aImgFrameWeight;
545    }
546               
547    // Set the shadow around the whole image
548    function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
549        $this->doshadow = $aShowShadow;
550        $this->shadow_color = $aShadowColor;
551        $this->shadow_width = $aShadowWidth;
552    }
553
554    // Specify x,y scale. Note that if you manually specify the scale
555    // you must also specify the tick distance with a call to Ticks::Set()
556    function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
557        $this->axtype = $aAxisType;
558
559        $yt=substr($aAxisType,-3,3);
560        if( $yt=="lin" )
561            $this->yscale = new LinearScale($aYMin,$aYMax);
562        elseif( $yt == "int" ) {
563            $this->yscale = new LinearScale($aYMin,$aYMax);
564            $this->yscale->SetIntScale();
565        }
566        elseif( $yt=="log" )
567            $this->yscale = new LogScale($aYMin,$aYMax);
568        else
569            JpGraphError::Raise("<b>JpGraph Error:</b> Unknown scale specification for Y-scale. ($axtype)");
570                       
571        $xt=substr($aAxisType,0,3);
572        if( $xt == "lin" || $xt == "tex" )
573            $this->xscale = new LinearScale($aXMin,$aXMax,"x");
574        elseif( $xt == "int" ) {
575            $this->xscale = new LinearScale($aXMin,$aXMax,"x");
576            $this->xscale->SetIntScale();
577        }
578        elseif( $xt == "log" )
579            $this->xscale = new LogScale($aXMin,$aXMax,"x");
580        else
581            JpGraphError::Raise("<b>JpGraph Error:</b> Unknown scale specification for X-scale. ($aAxisType)");
582
583        $this->xscale->Init($this->img);
584        $this->yscale->Init($this->img);                                               
585                                       
586        $this->xaxis = new Axis($this->img,$this->xscale);
587        $this->yaxis = new Axis($this->img,$this->yscale);
588        $this->xgrid = new Grid($this->xaxis);
589        $this->ygrid = new Grid($this->yaxis); 
590        $this->ygrid->Show();                   
591    }
592       
593    // Specify secondary Y scale
594    function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
595        if( $aAxisType=="lin" )
596            $this->y2scale = new LinearScale($aY2Min,$aY2Max);
597        elseif( $aAxisType=="log" ) {
598            $this->y2scale = new LogScale($aY2Min,$aY2Max);
599        }
600        else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
601                       
602        $this->y2scale->Init($this->img);       
603        $this->y2axis = new Axis($this->img,$this->y2scale);
604        $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
605        $this->y2axis->SetLabelPos(SIDE_RIGHT);
606               
607        // Deafult position is the max x-value
608        $this->y2axis->SetPos($this->xscale->GetMaxVal());
609        $this->y2grid = new Grid($this->y2axis);                                                       
610    }
611       
612    // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
613    // The dividing factor have been determined heuristically according to my aesthetic
614    // sense (or lack off) y.m.m.v !
615    function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
616        $this->xtick_factor=30;
617        $this->ytick_factor=25;         
618        switch( $aYDensity ) {
619            case TICKD_DENSE:
620                $this->ytick_factor=12;                 
621            break;
622            case TICKD_NORMAL:
623                $this->ytick_factor=25;                 
624            break;
625            case TICKD_SPARSE:
626                $this->ytick_factor=40;                 
627            break;
628            case TICKD_VERYSPARSE:
629                $this->ytick_factor=100;                       
630            break;             
631            default:
632                JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
633        }
634        switch( $aXDensity ) {
635            case TICKD_DENSE:
636                $this->xtick_factor=18;                                                 
637            break;
638            case TICKD_NORMAL:
639                $this->xtick_factor=30;                 
640            break;
641            case TICKD_SPARSE:
642                $this->xtick_factor=45;                                 
643            break;
644            case TICKD_VERYSPARSE:
645                $this->xtick_factor=60;                                                         
646            break;             
647            default:
648                JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
649        }               
650    }
651       
652    // Get a string of all image map areas     
653    function GetCSIMareas() {
654        $csim="";
655        foreach ($this->plots as $p) {
656            $csim.= $p->GetCSIMareas();
657        }               
658        return $csim;
659    }
660       
661    // Get a complete <MAP>..</MAP> tag for the final image map
662    function GetHTMLImageMap($aMapName) {
663        $im = "<MAP NAME=\"$aMapName\">\n";
664        $im .= $this->GetCSIMareas();
665        $im .= "</MAP>";
666        return $im;
667    }
668
669    // Stroke the graph
670    // $aStrokeFileName If != "" the image will be written to this file and NOT
671    // streamed back to the browser
672    function Stroke($aStrokeFileName="") {             
673               
674        // Do any pre-stroke adjustment that is needed by the different plot types
675        // (i.e bar plots want's to add an offset to the x-labels etc)
676        for($i=0; $i<count($this->plots)        ; ++$i ) {
677            $this->plots[$i]->PreStrokeAdjust($this);
678            $this->plots[$i]->Legend($this);
679        }
680               
681        // Any plots on the second Y scale?
682        if( $this->y2scale != null ) {
683            for($i=0; $i<count($this->y2plots)  ; ++$i ) {
684                $this->y2plots[$i]->PreStrokeAdjust($this);
685                $this->y2plots[$i]->Legend($this);
686            }
687        }
688               
689        // Bail out if any of the Y-axis not been specified and
690        // has no plots. (This means it is impossible to do autoscaling and
691        // no other scale was given so we can't possible draw anything). If you use manual
692        // scaling you also have to supply the tick steps as well.
693        if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
694            ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
695            JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified Y-scale.</strong><br>
696                                You have either:
697                                <br>* Specified an Y axis for autoscaling but have not supplied any plots
698                                <br>* Specified a scale manually but have forgot to specify the tick steps");
699        }
700               
701        // Bail out if no plots and no specified X-scale
702        if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
703            JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
704
705        //Check if we should autoscale y-axis
706        if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
707            list($min,$max) = $this->GetPlotsYMinMax($this->plots);
708            $this->yscale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
709        }
710
711        if( $this->y2scale != null)
712            if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
713                list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
714                $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
715            }                   
716                               
717        //Check if we should autoscale x-axis
718        if( !$this->xscale->IsSpecified() ) {
719            if( substr($this->axtype,0,4) == "text" ) {
720                $max=0;
721                foreach( $this->plots as $p )
722                    $max=max($max,$p->numpoints-1);
723                $min=0;
724                $this->xscale->Update($this->img,$min,$max);
725                $this->xscale->ticks->Set($this->xaxis->tick_step,1);
726                $this->xscale->ticks->SupressMinorTickMarks();
727            }
728            else {
729                list($min,$ymin) = $this->plots[0]->Min();
730                list($max,$ymax) = $this->plots[0]->Max();
731                foreach( $this->plots as $p ) {
732                    list($xmin,$ymin) = $p->Min();
733                    list($xmax,$ymax) = $p->Max();                     
734                    $min = Min($xmin,$min);
735                    $max = Max($xmax,$max);
736                }
737                $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
738            }
739                       
740            //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
741            $this->yaxis->SetPos($this->xscale->GetMinVal());
742            if( $this->y2axis != null ) {
743                $this->y2axis->SetPos($this->xscale->GetMaxVal());
744                $this->y2axis->SetTitleSide(SIDE_RIGHT);
745            }
746        }               
747               
748        // If we have a negative values and x-axis position is at 0
749        // we need to supress the first and possible the last tick since
750        // they will be drawn on top of the y-axis (and possible y2 axis)
751        // The test below might seem strange the reasone being that if
752        // the user hasn't specified a value for position this will not
753        // be set until we do the stroke for the axis so as of now it
754        // is undefined.
755               
756        if( !$this->xaxis->pos && $this->yscale->GetMinVal() < 0 ) {
757            $this->yscale->ticks->SupressZeroLabel(false);
758            $this->xscale->ticks->SupressFirst();
759            if( $this->y2axis != null ) {
760                $this->xscale->ticks->SupressLast();
761            }
762        }
763               
764        $this->StrokePlotArea();
765               
766        // Stroke axis
767        $this->xaxis->Stroke($this->yscale);
768        $this->yaxis->Stroke($this->xscale);           
769
770        // Stroke bands
771        if( $this->bands != null )
772            for($i=0; $i<count($this->bands); ++$i) {
773                                // Stroke all bands that asks to be in the background
774                if( $this->bands[$i]->depth == DEPTH_BACK )
775                    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
776            }
777
778        if( $this->grid_depth == DEPTH_BACK ) {
779            $this->ygrid->Stroke();
780            $this->xgrid->Stroke();
781        }
782                               
783        // Stroke Y2-axis
784        if( $this->y2axis != null ) {           
785            $this->y2axis->Stroke($this->xscale);                               
786            $this->y2grid->Stroke();
787        }
788               
789        $oldoff=$this->xscale->off;
790        if(substr($this->axtype,0,4)=="text") {
791            $this->xscale->off +=
792                 ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
793        }
794
795        // Stroke all plots for Y1 axis
796        for($i=0; $i<count($this->plots)        ; ++$i ) {
797            $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
798            $this->plots[$i]->StrokeMargin($this->img);
799        }                                               
800               
801        // Stroke all plots for Y2 axis
802        if( $this->y2scale != null )
803            for($i=0; $i< count($this->y2plots); ++$i ) {       
804                $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
805            }           
806
807        $this->xscale->off=$oldoff;
808               
809        if( $this->grid_depth == DEPTH_FRONT ) {
810            $this->ygrid->Stroke();
811            $this->xgrid->Stroke();
812        }
813
814        // Stroke bands
815        if( $this->bands!= null )
816            for($i=0; $i<count($this->bands); ++$i) {
817                                // Stroke all bands that asks to be in the foreground
818                if( $this->bands[$i]->depth == DEPTH_FRONT )
819                    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
820            }
821
822        // Stroke any lines added
823        if( $this->lines != null ) {
824            for($i=0; $i<count($this->lines); ++$i) {
825                $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
826            }
827        }
828               
829        // Finally draw the axis again since some plots may have nagged
830        // the axis in the edges.
831        $this->yaxis->Stroke($this->xscale);
832        $this->xaxis->Stroke($this->yscale);
833               
834        if( $this->y2scale != null)
835            $this->y2axis->Stroke($this->xscale);       
836               
837        $this->StrokePlotBox();
838               
839        // The titles and legends never gets rotated so make sure
840        // that the angle is 0 before stroking them                             
841        $aa = $this->img->SetAngle(0);
842        $this->StrokeTitles();
843        $this->legend->Stroke($this->img);             
844        $this->StrokeTexts();   
845        $this->img->SetAngle($aa);     
846                       
847        // Draw an outline around the image map
848        if(JPG_DEBUG)
849            $this->DisplayClientSideaImageMapAreas();           
850
851        // Adjust the appearance of the image
852        $this->AdjustSaturationBrightnessContrast();
853               
854        // Finally stream the generated picture                                 
855        $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);               
856    }
857
858//---------------
859// PRIVATE METHODS     
860    // Private helper function for backgound image
861    function LoadBkgImage($aImgFormat="png",$aBright=0,$aContr=0) {             
862        $f = "imagecreatefrom".$aImgFormat;
863        $imgtag = $aImgFormat;
864        if( $aImgFormat == "jpeg" )
865            $imgtag = "jpg";
866        if( !strstr($this->background_image,$imgtag) && strstr($this->background_image,".") )
867            JpGraphError::Raise("<b>JpGraph Error:</b> Background image seems to be of different type (has different file extension)
868                        than specified imagetype. <br>Specified: '".$aImgFormat."'<br>File: '".$this->background_image."'");
869        $img = $f($this->background_image);
870        if( !$img ) {
871            JpGraphError::Raise("<b>JpGraph Error:</b> Can't read background image: '".$this->background_image."'");   
872        }
873        return $img;
874    }   
875
876    // Private
877    // Stroke the plot area with either a solid color or a background image
878    function StrokePlotArea() {
879        // Copy in background image
880        if( $this->background_image != "" ) {
881            $bkgimg = $this->LoadBkgImage($this->background_image_format);
882            $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
883            $this->background_image_contr);                                                       
884            $this->img->_AdjSat($bkgimg,$this->background_image_sat);
885            $bw = ImageSX($bkgimg);
886            $bh = ImageSY($bkgimg);
887
888            $aa = $this->img->SetAngle(0);
889               
890            switch( $this->background_image_type ) {
891                case BGIMG_FILLPLOT: // Resize to just fill the plotarea
892                    $this->StrokeFrame();
893                $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
894                $this->img->left_margin,$this->img->top_margin,
895                0,0,$this->img->plotwidth,$this->img->plotheight,
896                $bw,$bh);
897                break;
898                case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
899                    $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
900                    0,0,0,0,
901                    $this->img->width,$this->img->height,
902                    $bw,$bh);
903                $this->StrokeFrame();
904                break;
905                case BGIMG_COPY: // Just copy the image from left corner, no resizing
906                    $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
907                    0,0,0,0,
908                    $bw,$bh,
909                    $bw,$bh);
910                $this->StrokeFrame();
911                break;
912                case BGIMG_CENTER: // Center original image in the plot area
913                    $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
914                $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
915                $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
916                $centerx,$centery,
917                0,0,
918                $bw,$bh,
919                $bw,$bh);
920                $this->StrokeFrame();
921                break;
922                default:
923                    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown background image layout");
924            }                   
925            $this->img->SetAngle($aa);                                                                                                 
926        }
927        else {                         
928            $aa = $this->img->SetAngle(0);
929            $this->StrokeFrame();
930            $this->img->SetAngle($aa);                 
931
932            $this->img->PushColor($this->plotarea_color);
933
934            // Note: To be consistent we really should take a possible shadow
935            // into account. However, that causes some problem for the LinearScale class
936            // since in the current design it does not have any links to class Graph which
937            // means it has no way of compensating for the adjusted plotarea in case of a
938            // shadow. So, until I redesign LinearScale we can't compensate for this.
939            // So just set the two adjustment parameters to zero for now.
940            $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
941            $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
942
943            $this->img->FilledRectangle($this->img->left_margin+$boxadj,
944              $this->img->top_margin+$boxadj,
945              $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
946              $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);     
947
948            $this->img->PopColor();
949        }       
950        $this->img->SetAngle($aa);
951    }   
952       
953       
954    function StrokePlotBox() {
955        // Should we draw a box around the plot area?
956        if( $this->boxed ) {
957            $this->img->SetLineWeight($this->box_weight);
958            $this->img->SetColor($this->box_color);
959            $this->img->Rectangle(
960                $this->img->left_margin,$this->img->top_margin,
961                $this->img->width-$this->img->right_margin,
962                $this->img->height-$this->img->bottom_margin);
963        }                                               
964    }           
965
966    function StrokeTitles() {
967        // Stroke title
968        $this->title->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,5);
969        $this->title->Stroke($this->img);
970               
971        // ... and subtitle
972        $this->subtitle->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,
973        7+$this->title->GetFontHeight($this->img));
974        $this->subtitle->Stroke($this->img);
975    }
976
977    function StrokeTexts() {
978        // Stroke any user added text objects
979        if( $this->texts != null ) {
980            for($i=0; $i<count($this->texts); ++$i) {
981                $this->texts[$i]->Stroke($this->img);
982            }
983        }
984    }
985
986    function DisplayClientSideaImageMapAreas() {
987        // Debug stuff - display the outline of the image map areas
988        foreach ($this->plots as $p) {
989            $csim.= $p->GetCSIMareas();
990        }
991        $csim.= $this->legend->GetCSIMareas();
992        if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
993            $this->img->SetColor($this->csimcolor);
994            for ($i=0; $i<count($coords[0]); $i++) {
995                if ($coords[1][$i]=="poly") {
996                    preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
997                    $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
998                    for ($j=0; $j<count($pts[0]); $j++) {
999                        $this->img->LineTo($pts[1][$j],$pts[2][$j]);
1000                    }
1001                } else if ($coords[1][$i]=="rect") {
1002                    $pts = preg_split('/,/', $coords[2][$i]);
1003                    $this->img->SetStartPoint($pts[0],$pts[1]);
1004                    $this->img->LineTo($pts[2],$pts[1]);
1005                    $this->img->LineTo($pts[2],$pts[3]);
1006                    $this->img->LineTo($pts[0],$pts[3]);
1007                    $this->img->LineTo($pts[0],$pts[1]);                                       
1008                }
1009            }
1010        }
1011    }
1012
1013    function AdjustSaturationBrightnessContrast() {
1014        // Adjust the brightness and contrast of the image
1015        if( $this->image_contr || $this->image_bright )
1016            $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
1017        if( $this->image_sat )                                                                                   
1018            $this->img->AdjSat($this->image_sat);
1019    }
1020
1021    // Text scale offset in world coordinates
1022    function SetTextScaleOff($aOff) {
1023        $this->text_scale_off = $aOff;
1024    }
1025       
1026    // Get min and max values for all included plots
1027    function GetPlotsYMinMax(&$aPlots) {
1028        list($xmax,$max) = $aPlots[0]->Max();
1029        list($xmin,$min) = $aPlots[0]->Min();
1030        for($i=0; $i<count($aPlots); ++$i ) {
1031            list($xmax,$ymax)=$aPlots[$i]->Max();
1032            list($xmin,$ymin)=$aPlots[$i]->Min();
1033            if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
1034            if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
1035        }
1036        if( $min == "" ) $min = 0;
1037        if( $max == "" ) $max = 0;
1038        if( $min == 0 && $max == 0 ) {
1039            // Special case if all values are 0
1040            $min=0;$max=1;                     
1041        }
1042        return array($min,$max);
1043    }
1044
1045    // Draw a frame around the image
1046    function StrokeFrame() {
1047        if( !$this->doframe ) return;
1048        if( $this->doshadow ) {
1049            $this->img->SetColor($this->frame_color);                   
1050            if( $this->background_image_type <= 1 )
1051                $c = $this->margin_color;
1052            else
1053                $c = false;
1054            $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
1055                                        $c,$this->shadow_width);
1056        }
1057        else {
1058            $this->img->SetLineWeight($this->frame_weight);
1059            if( $this->background_image_type <= 1 ) {
1060                $this->img->SetColor($this->margin_color);
1061                $this->img->FilledRectangle(1,1,$this->img->width-2,$this->img->height-2);             
1062            }
1063            $this->img->SetColor($this->frame_color);
1064            $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);               
1065        }
1066    }
1067} // Class
1068
1069
1070//===================================================
1071// CLASS TTF
1072// Description: Handle TTF font names
1073//===================================================
1074class TTF {
1075    var $font_fam;
1076//---------------
1077// CONSTRUCTOR
1078    function TTF() {
1079        // Base file names for available fonts
1080        $this->font_fam=array(
1081            FF_COURIER => TTF_DIR."courier",
1082            FF_VERDANA => TTF_DIR."verdana",
1083            FF_TIMES => TTF_DIR."times",
1084            FF_HANDWRT => TTF_DIR."handwriting",
1085            FF_COMIC => TTF_DIR."comic",
1086            FF_ARIAL => TTF_DIR."arial",
1087            FF_BOOK => TTF_DIR."bookant",
1088            FF_VERA => TTF_DIR."Vera");
1089    }
1090
1091//---------------
1092// PUBLIC METHODS       
1093    // Create the TTF file from the font specification
1094    function File($fam,$style=FS_NORMAL) {
1095        $f=$this->font_fam[$fam];
1096        if( !$f ) JpGraphError::Raise("<b>JpGraph Error:</b> Unknown TTF font family.");
1097        switch( $style ) {
1098            case FS_NORMAL:
1099                break;
1100            case FS_BOLD: $f .= "Bd";
1101                break;
1102            case FS_ITALIC: $f .= "It";
1103                break;
1104            case FS_BOLDIT: $f .= "BI";
1105                break;
1106            default:
1107                JpGraphError::Raise("<b>JpGraph Error:</b> Unknown TTF Style.");
1108        }
1109        $f .= ".ttf";
1110       
1111        // Check that file exist
1112        if( !file_exists($f) )
1113            JpGraphError::Raise("<b>JpGraph Error:</b> Can't open font file \"$f\". Wrong directory?");
1114               
1115        return $f;
1116    }
1117} // Class
1118
1119//===================================================
1120// CLASS LineProperty
1121// Description: Holds properties for a line
1122//===================================================
1123class LineProperty {
1124    var $iWeight=1, $iColor="black",$iStyle="solid";
1125    var $iShow=true;
1126       
1127//---------------
1128// PUBLIC METHODS       
1129    function SetColor($aColor) {
1130        $this->iColor = $aColor;
1131    }
1132       
1133    function SetWeight($aWeight) {
1134        $this->iWeight = $aWeight;
1135    }
1136       
1137    function SetStyle($aStyle) {
1138        $this->iStyle = $aStyle;
1139    }
1140               
1141    function Show($aShow=true) {
1142        $this->iShow=$aShow;
1143    }
1144       
1145    function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
1146        if( $this->iShow ) {
1147            $aImg->SetColor($this->iColor);
1148            $aImg->SetLineWeight($this->iWeight);
1149            $aImg->SetLineStyle($this->iStyle);                 
1150            $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
1151        }
1152    }
1153}
1154
1155
1156
1157//===================================================
1158// CLASS Text
1159// Description: Arbitrary text object that can be added to the graph
1160//===================================================
1161class Text {
1162    var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
1163    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$hide=false,$dir=0;
1164    var $boxed=false;   // Should the text be boxed
1165    var $paragraph_align="left";
1166
1167//---------------
1168// CONSTRUCTOR
1169
1170    // Create new text at absolute pixel coordinates
1171    function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
1172        $this->t = $aTxt;
1173        $this->x = $aXAbsPos;
1174        $this->y = $aYAbsPos;
1175    }
1176//---------------
1177// PUBLIC METHODS       
1178    // Set the string in the text object
1179    function Set($aTxt) {
1180        $this->t = $aTxt;
1181    }
1182       
1183    // Specify the position and alignment for the text object
1184    function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
1185        $this->x = $aXAbsPos;
1186        $this->y = $aYAbsPos;
1187        $this->halign = $aHAlign;
1188        $this->valign = $aVAlign;
1189    }
1190       
1191    // Specify alignment for the text
1192    function Align($aHAlign,$aVAlign="top") {
1193        $this->halign = $aHAlign;
1194        $this->valign = $aVAlign;
1195    }           
1196
1197    // Specifies the alignment for a multi line text
1198    function ParagraphAlign($aAlign) {
1199        $this->paragraph_align = $aAlign;
1200    }
1201       
1202    // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
1203    // $shadow=drop shadow should be added around the text.
1204    function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadow=false) {
1205        if( $aFrameColor==false )
1206            $this->boxed=false;
1207        else
1208            $this->boxed=true;
1209        $this->fcolor=$aFrameColor;
1210        $this->bcolor=$aBorderColor;
1211        $this->shadow=$aShadow;
1212    }
1213       
1214    // Hide the text
1215    function Hide($aHide=true) {
1216        $this->hide=$aHide;
1217    }
1218       
1219    // This looks ugly since it's not a very orthogonal design
1220    // but I added this "inverse" of Hide() to harmonize
1221    // with some classes which I designed more recently (especially)
1222    // jpgraph_gantt
1223    function Show($aShow=true) {
1224        $this->hide=!$aShow;
1225    }
1226       
1227    // Specify font
1228    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
1229        $this->font_family=$aFamily;
1230        $this->font_style=$aStyle;
1231        $this->font_size=$aSize;
1232    }
1233                       
1234    // Center the text between $left and $right coordinates
1235    function Center($aLeft,$aRight,$aYAbsPos=false) {
1236        $this->x = $aLeft + ($aRight-$aLeft     )/2;
1237        $this->halign = "center";
1238        if( is_numeric($aYAbsPos) )
1239            $this->y = $aYAbsPos;               
1240    }
1241       
1242    // Set text color
1243    function SetColor($aColor) {
1244        $this->color = $aColor;
1245    }
1246       
1247    // Orientation of text. Note only TTF fonts can have an arbitrary angle
1248    function SetOrientation($aDirection=0) {
1249        if( is_numeric($aDirection) )
1250            $this->dir=$aDirection;     
1251        elseif( $aDirection=="h" )
1252            $this->dir = 0;
1253        elseif( $aDirection=="v" )
1254            $this->dir = 90;
1255        else JpGraphError::Raise("<b>JpGraph Error:</b> Invalid direction specified for text.");
1256    }
1257       
1258    // Total width of text
1259    function GetWidth(&$aImg) {
1260        $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);         
1261        return $aImg->GetTextWidth($this->t);
1262    }
1263       
1264    // Hight of font
1265    function GetFontHeight(&$aImg) {
1266        $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);         
1267        return $aImg->GetFontHeight();
1268    }
1269
1270    function GetTextHeight(&$aImg) {
1271        $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);         
1272        return $aImg->GetTextHeight($this->t);
1273    }
1274       
1275    // Display text in image
1276    function Stroke(&$aImg,$x=-1,$y=-1) {
1277        if( $x>-1 ) $this->x = $x;
1278        if( $y>-1 ) $this->y = $y;
1279
1280        // If position been given as a fraction of the image size
1281        // calculate the absolute position
1282        if( $this->x < 1 ) $this->x *= $aImg->width;
1283        if( $this->y < 1 ) $this->y *= $aImg->height;
1284
1285        $aImg->PushColor($this->color);
1286        $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
1287        $aImg->SetTextAlign($this->halign,$this->valign);
1288        if( $this->boxed ) {
1289            if( $this->fcolor=="nofill" ) $this->fcolor=false;         
1290            $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
1291            $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
1292            $this->paragraph_align);
1293        }
1294        else {
1295            $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
1296            $this->paragraph_align);
1297        }
1298        $aImg->PopColor($this->color); 
1299    }
1300} // Class
1301
1302//===================================================
1303// CLASS Grid
1304// Description: responsible for drawing grid lines in graph
1305//===================================================
1306class Grid {
1307    var $img;
1308    var $scale;
1309    var $grid_color=array(196,196,196);
1310    var $type="solid";
1311    var $show=false, $showMinor=false,$weight=1;
1312//---------------
1313// CONSTRUCTOR
1314    function Grid(&$aAxis) {
1315        $this->scale = &$aAxis->scale;
1316        $this->img = &$aAxis->img;
1317    }
1318//---------------
1319// PUBLIC METHODS
1320    function SetColor($aColor) {
1321        $this->grid_color=$aColor;
1322    }
1323       
1324    function SetWeight($aWeight) {
1325        $this->weight=$aWeight;
1326    }
1327       
1328    // Specify if grid should be dashed, dotted or solid
1329    function SetLineStyle($aType) {
1330        $this->type = $aType;
1331    }
1332       
1333    // Decide if both major and minor grid should be displayed
1334    function Show($aShowMajor=true,$aShowMinor=false) {
1335        $this->show=$aShowMajor;
1336        $this->showMinor=$aShowMinor;
1337    }
1338       
1339    // Display the grid
1340    function Stroke() {
1341        if( $this->showMinor )
1342            $this->DoStroke($this->scale->ticks->ticks_pos);
1343        else
1344            $this->DoStroke($this->scale->ticks->maj_ticks_pos);
1345    }
1346       
1347//--------------
1348// Private methods     
1349    // Draw the grid
1350    function DoStroke(&$aTicksPos) {
1351        if( !$this->show )
1352            return;     
1353        $this->img->SetColor($this->grid_color);
1354        $this->img->SetLineWeight($this->weight);
1355        $nbrgrids = count($aTicksPos);                                 
1356        if( $this->scale->type=="y" ) {
1357            $xl=$this->img->left_margin;
1358            $xr=$this->img->width-$this->img->right_margin;
1359            for($i=0; $i<$nbrgrids; ++$i) {
1360                $y=$aTicksPos[$i];
1361                if( $this->type == "solid" )
1362                    $this->img->Line($xl,$y,$xr,$y);
1363                elseif( $this->type == "dotted" )
1364                    $this->img->DashedLine($xl,$y,$xr,$y,1,6);
1365                elseif( $this->type == "dashed" )
1366                    $this->img->DashedLine($xl,$y,$xr,$y,2,4);
1367                elseif( $this->type == "longdashed" )
1368                    $this->img->DashedLine($xl,$y,$xr,$y,8,6);
1369            }
1370        }
1371                               
1372        if( $this->scale->type=="x" ) {
1373            $yu=$this->img->top_margin;
1374            $yl=$this->img->height-$this->img->bottom_margin;
1375            $x=$aTicksPos[0];
1376            $limit=$this->img->width-$this->img->right_margin;
1377            $i=0;
1378            // We must also test for limit since we might have
1379            // an offset and the number of ticks is calculated with
1380            // assumption offset==0 so we might end up drawing one
1381            // to many gridlines
1382            while( $x<=$limit && $i<count($aTicksPos)) {
1383                $x=$aTicksPos[$i];
1384                if( $this->type == "solid" )                           
1385                    $this->img->Line($x,$yl,$x,$yu);
1386                elseif( $this->type == "dotted" )
1387                    $this->img->DashedLine($x,$yl,$x,$yu,1,6);
1388                elseif( $this->type == "dashed" )
1389                    $this->img->DashedLine($x,$yl,$x,$yu,2,4);
1390                elseif( $this->type == "longdashed" )
1391                    $this->img->DashedLine($x,$yl,$x,$yu,8,6);                                                                 
1392                ++$i;                                                                   
1393            }
1394        }               
1395        return true;
1396    }
1397} // Class
1398
1399//===================================================
1400// CLASS Axis
1401// Description: Defines X and Y axis. Notes that at the
1402// moment the code is not really good since the axis on
1403// several occasion must know wheter it's an X or Y axis.
1404// This was a design decision to make the code easier to
1405// follow.
1406//===================================================
1407class Axis {
1408    var $pos = false;
1409    var $weight=1;
1410    var $color=array(0,0,0),$label_color=array(0,0,0);
1411    var $img=null,$scale=null;
1412    var $hide=false;
1413    var $ticks_label=false;
1414    var $show_first_label=true;
1415    var $label_step=1; // Used by a text axis to specify what multiple of major steps
1416    // should be labeled.
1417    var $tick_step=1;
1418    var $labelPos=0;   // Which side of the axis should the labels be?
1419    var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
1420    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
1421    var $tick_label_margin=6;
1422//---------------
1423// CONSTRUCTOR
1424    function Axis(&$img,&$aScale,$color=array(0,0,0)) {
1425        $this->img = &$img;
1426        $this->scale = &$aScale;
1427        $this->color = $color;
1428        $this->title=new Text("");
1429               
1430        if( $aScale->type=="y" ) {
1431            $this->title_margin = 25;
1432            $this->title_adjust="middle";
1433            $this->title->SetOrientation(90);
1434            $this->tick_label_margin=6;
1435        }
1436        else {
1437            $this->title_margin = 5;
1438            $this->title_adjust="high";
1439            $this->title->SetOrientation(0);                   
1440            $this->tick_label_margin=3;
1441        }
1442    }
1443//---------------
1444// PUBLIC METHODS       
1445
1446    // Utility function to set the direction for tick marks
1447    function SetTickDirection($aDir) {
1448        $this->scale->ticks->SetDirection($aDir);
1449    }
1450       
1451    function SetLabelFormatString($aFormStr) {
1452        $this->scale->ticks->SetLabelFormat($aFormStr);
1453    }
1454       
1455    function SetLabelFormatCallback($aFuncName) {
1456        $this->scale->ticks->SetFormatCallback($aFuncName);
1457    }
1458
1459    // Don't display the first label
1460    function HideFirstTickLabel($aHide=false) {
1461        $this->show_first_label=$aHide;
1462    }
1463       
1464    // Hide the axis
1465    function Hide($aHide=true) {
1466        $this->hide=$aHide;
1467    }
1468
1469    // Weight of axis
1470    function SetWeight($aWeight) {
1471        $this->weight = $aWeight;
1472    }
1473
1474    // Axis color
1475    function SetColor($aColor,$aLabelColor=false) {
1476        $this->color = $aColor;
1477        if( !$aLabelColor ) $this->label_color = $aColor;
1478        else $this->label_color = $aLabelColor;
1479    }
1480       
1481    // Title on axis
1482    function SetTitle($aTitle,$aAdjustAlign="high") {
1483        $this->title->Set($aTitle);
1484        $this->title_adjust=$aAdjustAlign;
1485    }
1486       
1487    // Specify distance from the axis
1488    function SetTitleMargin($aMargin) {
1489        $this->title_margin=$aMargin;
1490    }
1491       
1492    // Specify text labels for the ticks. One label for each data point
1493    function SetTickLabels($aLabelArray) {
1494        $this->ticks_label = $aLabelArray;
1495    }
1496       
1497    // How far from the axis should the labels be drawn
1498    function SetTickLabelMargin($aMargin) {
1499        $this->tick_label_margin=$aMargin;
1500    }
1501       
1502    // Specify that every $step of the ticks should be displayed starting
1503    // at $start
1504    // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
1505    function SetTextTicks($step,$start=0) {
1506        JpGraphError::Raise("<b>JpGraph Error:</b> SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");         
1507    }
1508
1509    // Specify that every $step of the ticks should be displayed starting
1510    // at $start       
1511    function SetTextTickInterval($aStep,$aStart=0) {
1512        $this->scale->ticks->SetTextLabelStart($aStart);
1513        $this->tick_step=$aStep;
1514    }
1515       
1516    // Specify that every $step tick mark should have a label
1517    // should be displayed starting
1518    function SetTextLabelInterval($aStep) {
1519        if( $aStep < 1 )
1520            JpGraphError::Raise("<b>JpGraph Error:</b> Text label interval must be specified >= 1.");
1521        $this->label_step=$aStep;
1522    }
1523       
1524       
1525    // Which side of the axis should the labels be on?
1526    function SetLabelPos($aSidePos) {
1527        $this->labelPos=$aSidePos;
1528    }
1529       
1530    // Set the font
1531    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
1532        $this->font_family = $aFamily;
1533        $this->font_style = $aStyle;
1534        $this->font_size = $aSize;
1535    }
1536       
1537    // Which side of the axis should the axis title be?
1538    function SetTitleSide($aSideOfAxis) {
1539        $this->title_side = $aSideOfAxis;
1540    }
1541       
1542    // Stroke the axis.
1543    function Stroke($aOtherAxisScale) {         
1544        if( $this->hide ) return;               
1545        if( is_numeric($this->pos) ) {
1546            $pos=$aOtherAxisScale->Translate($this->pos);
1547        }
1548        else {  // Default to minimum of other scale if pos not set
1549            if( $aOtherAxisScale->GetMinVal() >= 0 || $this->pos=="min" ) {
1550                $pos = $aOtherAxisScale->scale_abs[0];
1551            }
1552            else { // If negative set x-axis at 0
1553                $this->pos=0;
1554                $pos=$aOtherAxisScale->Translate(0);
1555            }
1556        }       
1557        $this->img->SetLineWeight($this->weight);
1558        $this->img->SetColor($this->color);             
1559        $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
1560        if( $this->scale->type == "x" ) {
1561            $this->img->FilledRectangle($this->img->left_margin,$pos,
1562            $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
1563            $y=$pos+$this->img->GetFontHeight()+$this->title_margin;   
1564            if( $this->title_adjust=="high" )
1565                $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
1566            elseif($this->title_adjust=="middle")
1567                $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
1568            elseif($this->title_adjust=="low")
1569                $this->title->Pos($this->img->left_margin,$y,"left","top");
1570        }
1571        elseif( $this->scale->type == "y" ) {
1572            // Add line weight to the height of the axis since
1573            // the x-axis could have a width>1 and we want the axis to fit nicely together.
1574            $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
1575            $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
1576            $x=$pos ;
1577            if( $this->title_side == SIDE_LEFT ) {
1578                $x -= $this->title_margin;
1579                $halign="right";
1580            }
1581            else {
1582                $x += $this->title_margin;
1583                $halign="left";
1584            }
1585            if( $this->title_adjust=="high" )
1586                $this->title->Pos($x,$this->img->top_margin,$halign,"top");
1587            elseif($this->title_adjust=="middle" || $this->title_adjust=="center") 
1588                $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
1589            elseif($this->title_adjust=="low")
1590                $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");                           
1591        }
1592        $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
1593        $this->StrokeLabels($pos);
1594        $this->title->Stroke($this->img);
1595    }
1596
1597    // Position for axis line on the "other" scale
1598    function SetPos($aPosOnOtherScale) {
1599        $this->pos=$aPosOnOtherScale;
1600    }
1601       
1602    // Specify the angle for the tick labels
1603    function SetLabelAngle($aAngle) {
1604        $this->label_angle = $aAngle;
1605    }
1606       
1607//---------------
1608// PRIVATE METHODS     
1609    // Draw all the tick labels on major tick marks
1610    function StrokeLabels($aPos,$aMinor=false) {
1611        $this->img->SetColor($this->label_color);
1612        $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
1613        $yoff=$this->img->GetFontHeight()/2;
1614
1615        // Only draw labels at major tick marks
1616        $nbr = count($this->scale->ticks->maj_ticks_label);
1617
1618        // We have the option to not-display the very first mark
1619        // (Usefull when the first label might interfere with another
1620        // axis.)
1621        if( $this->show_first_label ) $start=0;
1622        else $start=1;
1623                       
1624        // Note. the $limit is only used for the x axis since we
1625        // might otherwise overshoot if the scale has been centered
1626        // This is due to us "loosing" the last tick mark if we center.
1627        //if( $this->scale->type=="x" )
1628        //      $limit=$this->img->width-$this->img->right_margin;
1629        //else
1630        //      $limit=$this->img->height;
1631                               
1632        // $i holds the current index for the label
1633        $i=$start;
1634               
1635        // Now run through all labels making sure we don't overshoot the end
1636        // of the scale.       
1637        while( $i<$nbr ) {
1638            // $tpos holds the absolute text position for the label
1639            $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];                                                                                                                 
1640            // we only draw every $label_step label
1641            if( ($i % $this->label_step)==0 ) {
1642                               
1643                                // If the label has been specified use that and in other case
1644                                // just label the mark with the actual scale value
1645                $m=$this->scale->ticks->GetMajor();
1646                               
1647                                // ticks_label has an entry for each data point
1648                if( isset($this->ticks_label[$i*$m]) )
1649                    $label=$this->ticks_label[$i*$m];
1650                else
1651                    $label=$this->scale->ticks->maj_ticks_label[$i];
1652                                       
1653                if( $this->scale->type == "x" ) {
1654                    if( $this->label_angle==0 || $this->label_angle==90 )
1655                        $this->img->SetTextAlign("center","top");
1656                    else
1657                        $this->img->SetTextAlign("topanchor","top");
1658                    $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,$this->label_angle);
1659                }
1660                else {
1661                    // scale->type == "y"
1662                    if( $this->label_angle!=0 )
1663                        JpGraphError::Raise("<b>JpGraph Error:</b> Labels at an angle are not supported on Y-axis");
1664                    if( $this->labelPos == 0 ) { // To the left of y-axis                                       
1665                        $this->img->SetTextAlign("right","center");
1666                        $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label);                                   
1667                    }
1668                    else { // To the right of the y-axis
1669                        $this->img->SetTextAlign("left","center");
1670                        $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label);                                   
1671                    }
1672                }
1673            }
1674            ++$i;       
1675        }                                                               
1676    }                   
1677
1678} // Class
1679
1680//===================================================
1681// CLASS Ticks
1682// Description: Abstract base class for drawing linear and logarithmic
1683// tick marks on axis
1684//===================================================
1685class Ticks {
1686    var $minor_abs_size=3, $major_abs_size=5;
1687    var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
1688    var $scale;
1689    var $is_set=false;
1690    var $precision=-1;
1691    var $supress_zerolabel=false,$supress_first=false;
1692    var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
1693    var $mincolor="",$majcolor="";
1694    var $weight=1;
1695    var $label_formatstr="";   // C-style format string to use for labels
1696    var $label_formfunc="";
1697
1698
1699//---------------
1700// CONSTRUCTOR
1701    function Ticks(&$aScale) {
1702        $this->scale=&$aScale;
1703    }
1704
1705//---------------
1706// PUBLIC METHODS       
1707    // Set format string for automatic labels
1708    function SetLabelFormat($aFormatString) {
1709        $this->label_formatstr=$aFormatString;
1710    }
1711       
1712    function SetFormatCallback($aCallbackFuncName) {
1713        $this->label_formfunc = $aCallbackFuncName;
1714    }
1715       
1716    // Don't display the first zero label
1717    function SupressZeroLabel($aFlag=true) {
1718        $this->supress_zerolabel=$aFlag;
1719    }
1720       
1721    // Don't display minor tick marks
1722    function SupressMinorTickMarks($aHide=true) {
1723        $this->supress_minor_tickmarks=$aHide;
1724    }
1725       
1726    // Don't display major tick marks
1727    function SupressTickMarks($aHide=true) {
1728        $this->supress_tickmarks=$aHide;
1729    }
1730       
1731    // Hide the first tick mark
1732    function SupressFirst($aHide=true) {
1733        $this->supress_first=$aHide;
1734    }
1735       
1736    // Hide the last tick mark
1737    function SupressLast($aHide=true) {
1738        $this->supress_last=$aHide;
1739    }
1740
1741    // Size (in pixels) of minor tick marks
1742    function GetMinTickAbsSize() {
1743        return $this->minor_abs_size;
1744    }
1745       
1746    // Size (in pixels) of major tick marks
1747    function GetMajTickAbsSize() {
1748        return $this->major_abs_size;           
1749    }
1750       
1751    // Have the ticks been specified
1752    function IsSpecified() {
1753        return $this->is_set;
1754    }
1755       
1756    // Set the distance between major and minor tick marks
1757    function Set($aMaj,$aMin) {
1758        // "Virtual method"
1759        // Should be implemented by the concrete subclass
1760        // if any action is wanted.
1761    }
1762       
1763    // Specify number of decimals in automtic labels
1764    // Deprecated from 1.4. Use SetFormatString() instead
1765    function SetPrecision($aPrecision) {
1766        $this->precision=$aPrecision;
1767    }
1768       
1769    // Which side of the axis should the ticks be on
1770    function SetDirection($aSide=SIDE_RIGHT) {
1771        $this->direction=$aSide;
1772    }
1773       
1774    // Set colors for major and minor tick marks
1775    function SetMarkColor($aMajorColor,$aMinorColor="") {
1776        $this->majcolor=$aMajorColor;
1777               
1778        // If not specified use same as major
1779        if( $aMinorColor=="" )
1780            $this->mincolor=$aMajorColor;
1781        else
1782            $this->mincolor=$aMinorColor;
1783    }
1784       
1785    function SetWeight($aWeight) {
1786        $this->weight=$aWeight;
1787    }
1788       
1789} // Class
1790
1791//===================================================
1792// CLASS LinearTicks
1793// Description: Draw linear ticks on axis
1794//===================================================
1795class LinearTicks extends Ticks {
1796    var $minor_step=1, $major_step=2;
1797    var $xlabel_offset=0,$xtick_offset=0;
1798    var $label_offset=0; // What offset should the displayed label have
1799    // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
1800    var $text_label_start=0;
1801//---------------
1802// CONSTRUCTOR
1803    function LinearTicks() {
1804        // Empty
1805    }
1806
1807//---------------
1808// PUBLIC METHODS       
1809       
1810       
1811    // Return major step size in world coordinates
1812    function GetMajor() {
1813        return $this->major_step;
1814    }
1815       
1816    // Return minor step size in world coordinates
1817    function GetMinor() {
1818        return $this->minor_step;
1819    }
1820       
1821    // Set Minor and Major ticks (in world coordinates)
1822    function Set($aMajStep,$aMinStep) {
1823        if( $aMajStep <= 0 || $aMinStep <= 0 ) {
1824            JpGraphError::Raise("<b>JpGraph Error:</b> Minor or major step size is 0. Check that you haven't
1825                                got an accidental SetTextTicks(0) in your code.<p>
1826                                If this is not the case you might have stumbled upon a bug in JpGraph.
1827                                Please report this and if possible include the data that caused the
1828                                problem.");
1829        }
1830               
1831        $this->major_step=$aMajStep;
1832        $this->minor_step=$aMinStep;
1833        $this->is_set = true;
1834    }
1835
1836    // Draw linear ticks
1837    function Stroke(&$img,&$scale,$pos) {
1838        $maj_step_abs = $scale->scale_factor*$this->major_step;         
1839        $min_step_abs = $scale->scale_factor*$this->minor_step;         
1840
1841        if( $min_step_abs==0 || $maj_step_abs==0 )
1842            JpGraphError::Raise("<b>JpGraph Error:</b> A plot has an illegal scale. This could for example be
1843                        that you are trying to use text autoscaling to draw a line plot with only one point
1844                        or similair abnormality (a line needs two points!).");
1845        $limit = $scale->scale_abs[1]; 
1846        $nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
1847        $first=0;
1848               
1849        // If precision hasn't been specified set it to a sensible value
1850        if( $this->precision==-1 ) {
1851            $t = log10($this->minor_step);
1852            if( $t > 0 )
1853                $precision = 0;
1854            else
1855                $precision = -floor($t);
1856        }
1857        else
1858            $precision = $this->precision;
1859                       
1860        $img->SetLineWeight($this->weight);                     
1861               
1862        // Handle ticks on X-axis
1863        if( $scale->type == "x" ) {
1864            // Draw the major tick marks
1865                       
1866            $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
1867                       
1868            // TODO: Add logic to set label_offset for text labels
1869            $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;   
1870                       
1871            $start_abs=$scale->scale_factor*$this->text_label_start;
1872                       
1873            $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;                 
1874            for( $i=0; $label<=$scale->GetMaxVal()+$this->label_offset; ++$i ) {
1875                $x=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xlabel_offset*$min_step_abs;                         
1876                $this->maj_ticklabels_pos[$i]=ceil($x);                         
1877                               
1878                                // Apply format
1879                if( $this->label_formfunc != "" ) {
1880                    $f=$this->label_formfunc;
1881                    $l = $f($label);
1882                }       
1883                elseif( $this->label_formatstr != "" )
1884                    $l = sprintf($this->label_formatstr,$label);
1885                else
1886                    $l = sprintf("%01.".$precision."f",round($label,$precision));
1887                                       
1888                if( ($this->supress_zerolabel && ($l + 0)==0) ||
1889                    ($this->supress_first && $i==0) ||
1890                    ($this->supress_last  && $i==$nbrmajticks-1) )
1891                    $l="";                                     
1892                $this->maj_ticks_label[$i]=$l;
1893                $label+=$this->major_step;
1894                               
1895                                // The x-position of the tick marks can be different from the labels.
1896                                // Note that we record the tick position (not the label) so that the grid
1897                                // happen upon tick marks and not labels.
1898                $xtick=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xtick_offset*$min_step_abs;
1899                $this->maj_ticks_pos[$i]=ceil($xtick);                         
1900                if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
1901                    if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
1902                    $img->Line($xtick,$pos,$xtick,$yu);
1903                    if( $this->majcolor!="" ) $img->PopColor();
1904                }
1905            }
1906            // Draw the minor tick marks
1907                       
1908            $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
1909            $label = $scale->GetMinVal();                                                               
1910            for( $i=0,$x=$scale->scale_abs[0]; $x<$limit; ++$i ) {
1911                $x=$scale->scale_abs[0]+$i*$min_step_abs;
1912                $this->ticks_pos[]=$x;
1913                $this->ticks_label[]=$label;
1914                $label+=$this->minor_step;
1915                if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)       {                                               
1916                    if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
1917                    $img->Line($x,$pos,$x,$yu);
1918                    if( $this->mincolor!="" ) $img->PopColor();
1919                }
1920            }
1921        }
1922        elseif( $scale->type == "y" ) {
1923            // Draw the major tick marks
1924            $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
1925            $label = $scale->GetMinVal();
1926            for( $i=0; $i<$nbrmajticks; ++$i) {
1927                $y=$scale->scale_abs[0]+$i*$maj_step_abs;                               
1928                               
1929                                // THe following two lines might seem to be unecessary but they are not!
1930                                // The reason being that for X-axis we separate the position of the labels
1931                                // and the tick marks which we don't do for the Y-axis.
1932                                // We therefore need to make sure both arrays are corcectly filled
1933                                // since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
1934                                // the tick positions.
1935                $this->maj_ticklabels_pos[$i]=$y;
1936                $this->maj_ticks_pos[$i]=$y;
1937               
1938                if( $this->label_formfunc != "" ) {
1939                    $f=$this->label_formfunc;
1940                    $l = $f($label);
1941                }       
1942                elseif( $this->label_formatstr != "" )
1943                    $l = sprintf($this->label_formatstr,$label);
1944                else
1945                    $l = sprintf("%01.".$precision."f",round($label,$precision));
1946                                               
1947                if( ($this->supress_zerolabel && ($l + 0)==0) ||
1948                    ($this->supress_first && $i==0) ||
1949                    ($this->supress_last  && $i==$nbrmajticks-1) )
1950                    $l="";
1951                $this->maj_ticks_label[$i]=$l;
1952                $label+=$this->major_step;     
1953                if( !$this->supress_tickmarks ) {
1954                    if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
1955                    $img->Line($pos,$y,$xr,$y);
1956                    if( $this->majcolor!="" ) $img->PopColor();
1957                }
1958            }
1959            // Draw the minor tick marks
1960            $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
1961            $label = $scale->GetMinVal();       
1962            for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
1963                $this->ticks_pos[$i]=$y;
1964                $this->ticks_label[$i]=$label;                         
1965                $label+=$this->minor_step;                             
1966                if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)       {
1967                    if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
1968                    $img->Line($pos,$y,$xr,$y);
1969                    if( $this->mincolor!="" ) $img->PopColor();
1970                }
1971                ++$i;
1972                $y=$scale->scale_abs[0]+$i*$min_step_abs;                                                               
1973            }   
1974        }       
1975    }
1976//---------------
1977// PRIVATE METHODS
1978    // Spoecify the offset of the displayed tick mark with the tick "space"
1979    // Legal values for $o is [0,1] used to adjust where the tick marks and label
1980    // should be positioned within the major tick-size
1981    // $lo specifies the label offset and $to specifies the tick offset
1982    // this comes in handy for example in bar graphs where we wont no offset for the
1983    // tick but have the labels displayed halfway under the bars.
1984    function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
1985        $this->xlabel_offset=$aLabelOff;
1986        if( $aTickOff==-1 )     // Same as label offset
1987            $this->xtick_offset=$aLabelOff;
1988        else
1989            $this->xtick_offset=$aTickOff;
1990        if( $aLabelOff>0 )
1991            $this->SupressLast();       // The last tick wont fit
1992    }
1993
1994    // Which tick label should we start with?
1995    function SetTextLabelStart($aTextLabelOff) {
1996        $this->text_label_start=$aTextLabelOff;
1997    }
1998       
1999} // Class
2000
2001//===================================================
2002// CLASS LinearScale
2003// Description: Handle linear scaling between screen and world
2004//===================================================
2005class LinearScale {
2006    var $scale=array(0,0);
2007    var $scale_abs=array(0,0);
2008    var $scale_factor; // Scale factor between world and screen
2009    var $world_size;    // Plot area size in world coordinates
2010    var $world_abs_size; // Plot area size in pixels
2011    var $off; // Offset between image edge and plot area
2012    var $type; // is this x or y scale ?
2013    var $ticks=null; // Store ticks
2014    var $autoscale_min=false; // Forced minimum value, useful to let user force 0 as start and autoscale max
2015    var $gracetop=0,$gracebottom=0;
2016    var $intscale=false; // Restrict autoscale to integers
2017//---------------
2018// CONSTRUCTOR
2019    function LinearScale($aMin=0,$aMax=0,$aType="y") {
2020        assert($aType=="x" || $aType=="y" );
2021        assert($aMin<=$aMax);
2022               
2023        $this->type=$aType;
2024        $this->scale=array($aMin,$aMax);               
2025        $this->world_size=$aMax-$aMin; 
2026        $this->ticks = new LinearTicks();
2027    }
2028
2029//---------------
2030// PUBLIC METHODS       
2031    // Second phase constructor
2032    function Init(&$aImg) {
2033        $this->InitConstants($aImg);   
2034        // We want image to notify us when the margins changes so we
2035        // can recalculate the constants.
2036        // PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
2037        // SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
2038        // TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
2039        // GET ALL THE NOTIFICATIONS. (This took a while to track down...)
2040               
2041        // Add us as an observer to class Image
2042        $aImg->AddObserver("InitConstants",$this);
2043    }
2044       
2045    // Check if scale is set or if we should autoscale
2046    // We should do this is either scale or ticks has not been set
2047    function IsSpecified() {
2048        if( $this->GetMinVal()==$this->GetMaxVal() ) {          // Scale not set
2049            return false;
2050        }
2051        return true;
2052    }
2053       
2054    // Set the minimum data value when the autoscaling is used.
2055    // Usefull if you want a fix minimum (like 0) but automtic maximum
2056    function SetAutoMin($aMin) {
2057        $this->autoscale_min=$aMin;
2058    }
2059       
2060    // Specify scale "grace" value (top and bottom)
2061    function SetGrace($aGraceTop,$aGraceBottom=0) {
2062        if( $aGraceTop<0 || $aGraceBottom < 0  )
2063            JpGraphError::Raise("<b>JpGraph Error:</b> Grace must be larger then 0");
2064        $this->gracetop=$aGraceTop;
2065        $this->gracebottom=$aGraceBottom;
2066    }
2067       
2068    // Get the minimum value in the scale
2069    function GetMinVal() {
2070        return $this->scale[0];
2071    }
2072       
2073    // get maximum value for scale
2074    function GetMaxVal() {
2075        return $this->scale[1];
2076    }
2077               
2078    // Specify a new min/max value for sclae   
2079    function Update(&$aImg,$aMin,$aMax) {
2080        $this->scale=array($aMin,$aMax);               
2081        $this->world_size=$aMax-$aMin;         
2082        $this->InitConstants($aImg);                                   
2083    }
2084       
2085    // Translate between world and screen
2086    function Translate($aCoord) {
2087        return $this->off+round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0);
2088    }
2089       
2090    // Relative translate (don't include offset) usefull when we just want
2091    // to know the relative position (in pixels) on the axis
2092    function RelTranslate($aCoord) {
2093        return round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0);
2094    }
2095       
2096    // Restrict autoscaling to only use integers
2097    function SetIntScale($aIntScale=true) {
2098        $this->intscale=$aIntScale;
2099    }
2100       
2101    // Calculate an integer autoscale
2102    function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
2103        // Make sure limits are integers
2104        $min=floor($min);
2105        $max=ceil($max);
2106        if( abs($min-$max)==0 ) {
2107            --$min; ++$max;
2108        }
2109        $gracetop=ceil(($this->gracetop/100.0))*abs($max-$min);
2110        $gracebottom=ceil(($this->gracebottom/100.0))*abs($max-$min);
2111        if( is_numeric($this->autoscale_min) ) {
2112            $min = ceil($this->autoscale_min);
2113            if( abs($min-$max ) == 0 ) {
2114                ++$max;
2115                --$min;
2116            }
2117        }
2118               
2119        $min -= $gracebottom;
2120        $max += $gracetop;             
2121
2122        // First get tickmarks as multiples of 1, 10, ...       
2123        list($num1steps,$adj1min,$adj1max,$maj1step) =
2124            $this->IntCalcTicks($maxsteps,$min,$max,1);
2125               
2126        // Then get tick marks as 2:s 2, 20, ...
2127        list($num2steps,$adj2min,$adj2max,$maj2step) =
2128            $this->IntCalcTicks($maxsteps,$min,$max,5);
2129               
2130        // Then get tickmarks as 5:s 5, 50, 500, ...
2131        list($num5steps,$adj5min,$adj5max,$maj5step) =
2132            $this->IntCalcTicks($maxsteps,$min,$max,2);         
2133
2134        // Check to see whichof 1:s, 2:s or 5:s fit better with
2135        // the requested number of major ticks         
2136        $match1=abs($num1steps-$maxsteps);             
2137        $match2=abs($num2steps-$maxsteps);
2138        if( $maj5step > 1 )
2139            $match5=abs($num5steps-$maxsteps);
2140        else
2141            $match5=1000000;    // Dummy high value
2142               
2143        // Compare these three values and see which is the closest match
2144        // We use a 0.6 weight to gravitate towards multiple of 5:s
2145        if( $match1 < $match2 ) {
2146            if( $match1 < $match5 )
2147                $r=1;                   
2148            else
2149                $r=3;
2150        }
2151        else {
2152            if( $match2 < $match5 )
2153                $r=2;                   
2154            else
2155                $r=3;           
2156        }       
2157               
2158        // Minsteps are always the same as maxsteps for integer scale
2159        switch( $r ) {
2160            case 1:
2161                $this->Update($img,$adj1min,$adj1max);
2162            $this->ticks->Set($maj1step,$maj1step);
2163            break;                     
2164            case 2:
2165                $this->Update($img,$adj2min,$adj2max);         
2166            $this->ticks->Set($maj2step,$maj2step);
2167            break;                                                                     
2168            case 3:
2169                $this->Update($img,$adj5min,$adj5max);
2170            $this->ticks->Set($maj5step,$maj2step);             
2171            break;                     
2172        }               
2173    }
2174       
2175       
2176    // Calculate autoscale. Used if user hasn't given a scale and ticks
2177    // $maxsteps is the maximum number of major tickmarks allowed.
2178    function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
2179        if( $this->intscale ) {
2180            $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
2181            return;
2182        }
2183        if( abs($min-$max) < 0.00001 ) {
2184            // We need some difference to be able to autoscale
2185            // make it 5% above and 5% below value
2186            if( $min==0 && $max==0 ) {          // Special case
2187                $min=-1; $max=1;
2188            }
2189            else {
2190                $delta = abs($max-$min)*0.05;
2191                $min -= $delta;
2192                $max += $delta;
2193            }
2194        }
2195               
2196        $gracetop=($this->gracetop/100.0)*abs($max-$min);
2197        $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
2198        if( is_numeric($this->autoscale_min) ) {
2199            $min = $this->autoscale_min;
2200            if( abs($min-$max ) < 0.00001 )
2201                $max *= 1.05;
2202        }
2203        $min -= $gracebottom;
2204        $max += $gracetop;
2205
2206        // First get tickmarks as multiples of 0.1, 1, 10, ... 
2207        list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
2208            $this->CalcTicks($maxsteps,$min,$max,1,2);
2209               
2210        // Then get tick marks as 2:s 0.2, 2, 20, ...
2211        list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
2212            $this->CalcTicks($maxsteps,$min,$max,5,2);
2213               
2214        // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
2215        list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
2216            $this->CalcTicks($maxsteps,$min,$max,2,5);         
2217
2218        // Check to see whichof 1:s, 2:s or 5:s fit better with
2219        // the requested number of major ticks         
2220        $match1=abs($num1steps-$maxsteps);             
2221        $match2=abs($num2steps-$maxsteps);
2222        $match5=abs($num5steps-$maxsteps);
2223        // Compare these three values and see which is the closest match
2224        // We use a 0.8 weight to gravitate towards multiple of 5:s
2225        $r=$this->MatchMin3($match1,$match2,$match5,0.8);
2226        switch( $r ) {
2227            case 1:
2228                $this->Update($img,$adj1min,$adj1max);
2229            $this->ticks->Set($maj1step,$min1step);
2230            break;                     
2231            case 2:
2232                $this->Update($img,$adj2min,$adj2max);         
2233            $this->ticks->Set($maj2step,$min2step);
2234            break;                                                                     
2235            case 3:
2236                $this->Update($img,$adj5min,$adj5max);
2237            $this->ticks->Set($maj5step,$min5step);             
2238            break;                     
2239        }
2240    }
2241
2242//---------------
2243// PRIVATE METHODS     
2244
2245    // This method recalculates all constants that are depending on the
2246    // margins in the image. If the margins in the image are changed
2247    // this method should be called for every scale that is registred with
2248    // that image. Should really be installed as an observer of that image.
2249    function InitConstants(&$img) {
2250        if( $this->type=="x" ) {
2251            $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
2252            $this->off=$img->left_margin;
2253            $this->scale_factor = 0;
2254            if( $this->world_size > 0 )
2255                $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
2256        }
2257        else { // y scale
2258            $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
2259            $this->off=$img->top_margin+$this->world_abs_size;                 
2260            $this->scale_factor = 0;                   
2261            if( $this->world_size > 0 )                 
2262                $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);     
2263        }
2264        $size = $this->world_size * $this->scale_factor;
2265        $this->scale_abs=array($this->off,$this->off + $size); 
2266    }
2267       
2268    // Initialize the conversion constants for this scale
2269    // This tries to pre-calculate as much as possible to speed up the
2270    // actual conversion (with Translate()) later on
2271    // $start   =scale start in absolute pixels (for x-scale this is an y-position
2272    //                           and for an y-scale this is an x-position
2273    // $len             =absolute length in pixels of scale                     
2274    function SetConstants($aStart,$aLen) {
2275        $this->world_abs_size=$aLen;
2276        $this->off=$aStart;
2277               
2278        if( $this->world_size<=0 ) {
2279            JpGraphError::Raise("<b>JpGraph Fatal Error</b>:<br>
2280                 You have unfortunately stumbled upon a bug in JpGraph. <br>
2281                 It seems like the scale range is ".$this->world_size." [for ".
2282                 $this->type." scale] <br>
2283                 Please report Bug #01 to jpgraph@aditus.nu and include the script
2284                 that gave this error. <br>
2285                 This problem could potentially be caused by trying to use \"illegal\"
2286                 values in the input data arrays (like trying to send in strings or
2287                 only NULL values) which causes the autoscaling to fail.");
2288        }
2289               
2290        // scale_factor = number of pixels per world unit
2291        $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
2292               
2293        // scale_abs = start and end points of scale in absolute pixels
2294        $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);           
2295    }
2296       
2297       
2298    // Calculate number of ticks steps with a specific division
2299    // $a is the divisor of 10**x to generate the first maj tick intervall
2300    // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
2301    // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
2302    // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
2303    // We return a vector of
2304    //  [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
2305    // If $majend==true then the first and last marks on the axis will be major
2306    // labeled tick marks otherwise it will be adjusted to the closest min tick mark
2307    function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
2308        $diff=$max-$min;
2309        if( $diff==0 )
2310            $ld=0;
2311        else
2312            $ld=floor(log10($diff));
2313               
2314        // Gravitate min towards zero if we are close           
2315        if( $min>0 && $min < pow(10,$ld) ) $min=0;
2316               
2317        $majstep=pow(10,$ld-1)/$a;
2318        $minstep=$majstep/$b;
2319        $adjmax=ceil($max/$minstep)*$minstep;
2320        $adjmin=floor($min/$minstep)*$minstep; 
2321        $adjdiff = $adjmax-$adjmin;
2322        $numsteps=$adjdiff/$majstep;
2323        while( $numsteps>$maxsteps ) {
2324            $majstep=pow(10,$ld)/$a;
2325            $numsteps=$adjdiff/$majstep;
2326            ++$ld;
2327        }
2328               
2329        $minstep=$majstep/$b;
2330        $adjmin=floor($min/$minstep)*$minstep; 
2331        $adjdiff = $adjmax-$adjmin;             
2332        if( $majend ) {
2333            $adjmin = floor($min/$majstep)*$majstep;   
2334            $adjdiff = $adjmax-$adjmin;         
2335            $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
2336        }
2337        else
2338            $adjmax=ceil($max/$minstep)*$minstep;
2339                       
2340        return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
2341    }
2342       
2343    function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
2344        $diff=$max-$min;
2345        if( $diff==0 )
2346            $ld=0;
2347        else
2348            $ld=floor(log10($diff));
2349               
2350        // Gravitate min towards zero if we are close           
2351        if( $min>0 && $min < pow(10,$ld) ) $min=0;
2352               
2353        $majstep=pow(10,$ld-1)/$a;
2354        $adjmax=ceil($max/$majstep)*$majstep;
2355        $adjmin=floor($min/$majstep)*$majstep; 
2356        $adjdiff = $adjmax-$adjmin;
2357        $numsteps=$adjdiff/$majstep;
2358        while( $numsteps>$maxsteps ) {
2359            $majstep=pow(10,$ld)/$a;
2360            $numsteps=$adjdiff/$majstep;
2361            ++$ld;
2362        }
2363               
2364        $adjmin=floor($min/$majstep)*$majstep; 
2365        $adjdiff = $adjmax-$adjmin;             
2366        if( $majend ) {
2367            $adjmin = floor($min/$majstep)*$majstep;   
2368            $adjdiff = $adjmax-$adjmin;         
2369            $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
2370        }
2371        else
2372            $adjmax=ceil($max/$majstep)*$majstep;
2373                       
2374        return array($numsteps,$adjmin,$adjmax,$majstep);               
2375    }
2376
2377
2378       
2379    // Determine the minimum of three values witha  weight for last value
2380    function MatchMin3($a,$b,$c,$weight) {
2381        if( $a < $b ) {
2382            if( $a < ($c*$weight) )
2383                return 1; // $a smallest
2384            else
2385                return 3; // $c smallest
2386        }
2387        elseif( $b < ($c*$weight) )
2388            return 2; // $b smallest
2389        return 3; // $c smallest
2390    }
2391} // Class
2392
2393//===================================================
2394// CLASS RGB
2395// Description: Color definitions as RGB triples
2396//===================================================
2397class RGB {
2398    var $rgb_table;
2399    var $img;
2400    function RGB(&$aImg) {
2401        $this->img = $aImg;
2402               
2403        // Conversion array between color names and RGB
2404        $this->rgb_table = array(
2405            "aqua"=> array(0,255,255),         
2406            "lime"=> array(0,255,0),           
2407            "teal"=> array(0,128,128),
2408            "whitesmoke"=>array(245,245,245),
2409            "gainsboro"=>array(220,220,220),
2410            "oldlace"=>array(253,245,230),
2411            "linen"=>array(250,240,230),
2412            "antiquewhite"=>array(250,235,215),
2413            "papayawhip"=>array(255,239,213),
2414            "blanchedalmond"=>array(255,235,205),
2415            "bisque"=>array(255,228,196),
2416            "peachpuff"=>array(255,218,185),
2417            "navajowhite"=>array(255,222,173),
2418            "moccasin"=>array(255,228,181),
2419            "cornsilk"=>array(255,248,220),
2420            "ivory"=>array(255,255,240),
2421            "lemonchiffon"=>array(255,250,205),
2422            "seashell"=>array(255,245,238),
2423            "mintcream"=>array(245,255,250),
2424            "azure"=>array(240,255,255),
2425            "aliceblue"=>array(240,248,255),
2426            "lavender"=>array(230,230,250),
2427            "lavenderblush"=>array(255,240,245),
2428            "mistyrose"=>array(255,228,225),
2429            "white"=>array(255,255,255),
2430            "black"=>array(0,0,0),
2431            "darkslategray"=>array(47,79,79),
2432            "dimgray"=>array(105,105,105),
2433            "slategray"=>array(112,128,144),
2434            "lightslategray"=>array(119,136,153),
2435            "gray"=>array(190,190,190),
2436            "lightgray"=>array(211,211,211),
2437            "midnightblue"=>array(25,25,112),
2438            "navy"=>array(0,0,128),
2439            "cornflowerblue"=>array(100,149,237),
2440            "darkslateblue"=>array(72,61,139),
2441            "slateblue"=>array(106,90,205),
2442            "mediumslateblue"=>array(123,104,238),
2443            "lightslateblue"=>array(132,112,255),
2444            "mediumblue"=>array(0,0,205),
2445            "royalblue"=>array(65,105,225),
2446            "blue"=>array(0,0,255),
2447            "dodgerblue"=>array(30,144,255),
2448            "deepskyblue"=>array(0,191,255),
2449            "skyblue"=>array(135,206,235),
2450            "lightskyblue"=>array(135,206,250),
2451            "steelblue"=>array(70,130,180),
2452            "lightred"=>array(211,167,168),
2453            "lightsteelblue"=>array(176,196,222),
2454            "lightblue"=>array(173,216,230),
2455            "powderblue"=>array(176,224,230),
2456            "paleturquoise"=>array(175,238,238),
2457            "darkturquoise"=>array(0,206,209),
2458            "mediumturquoise"=>array(72,209,204),
2459            "turquoise"=>array(64,224,208),
2460            "cyan"=>array(0,255,255),
2461            "lightcyan"=>array(224,255,255),
2462            "cadetblue"=>array(95,158,160),
2463            "mediumaquamarine"=>array(102,205,170),
2464            "aquamarine"=>array(127,255,212),
2465            "darkgreen"=>array(0,100,0),
2466            "darkolivegreen"=>array(85,107,47),
2467            "darkseagreen"=>array(143,188,143),
2468            "seagreen"=>array(46,139,87),
2469            "mediumseagreen"=>array(60,179,113),
2470            "lightseagreen"=>array(32,178,170),
2471            "palegreen"=>array(152,251,152),
2472            "springgreen"=>array(0,255,127),
2473            "lawngreen"=>array(124,252,0),
2474            "green"=>array(0,255,0),
2475            "chartreuse"=>array(127,255,0),
2476            "mediumspringgreen"=>array(0,250,154),
2477            "greenyellow"=>array(173,255,47),
2478            "limegreen"=>array(50,205,50),
2479            "yellowgreen"=>array(154,205,50),
2480            "forestgreen"=>array(34,139,34),
2481            "olivedrab"=>array(107,142,35),
2482            "darkkhaki"=>array(189,183,107),
2483            "khaki"=>array(240,230,140),
2484            "palegoldenrod"=>array(238,232,170),
2485            "lightgoldenrodyellow"=>array(250,250,210),
2486            "lightyellow"=>array(255,255,200),
2487            "yellow"=>array(255,255,0),
2488            "gold"=>array(255,215,0),
2489            "lightgoldenrod"=>array(238,221,130),
2490            "goldenrod"=>array(218,165,32),
2491            "darkgoldenrod"=>array(184,134,11),
2492            "rosybrown"=>array(188,143,143),
2493            "indianred"=>array(205,92,92),
2494            "saddlebrown"=>array(139,69,19),
2495            "sienna"=>array(160,82,45),
2496            "peru"=>array(205,133,63),
2497            "burlywood"=>array(222,184,135),
2498            "beige"=>array(245,245,220),
2499            "wheat"=>array(245,222,179),
2500            "sandybrown"=>array(244,164,96),
2501            "tan"=>array(210,180,140),
2502            "chocolate"=>array(210,105,30),
2503            "firebrick"=>array(178,34,34),
2504            "brown"=>array(165,42,42),
2505            "darksalmon"=>array(233,150,122),
2506            "salmon"=>array(250,128,114),
2507            "lightsalmon"=>array(255,160,122),
2508            "orange"=>array(255,165,0),
2509            "darkorange"=>array(255,140,0),
2510            "coral"=>array(255,127,80),
2511            "lightcoral"=>array(240,128,128),
2512            "tomato"=>array(255,99,71),
2513            "orangered"=>array(255,69,0),
2514            "red"=>array(255,0,0),
2515            "hotpink"=>array(255,105,180),
2516            "deeppink"=>array(255,20,147),
2517            "pink"=>array(255,192,203),
2518            "lightpink"=>array(255,182,193),
2519            "palevioletred"=>array(219,112,147),
2520            "maroon"=>array(176,48,96),
2521            "mediumvioletred"=>array(199,21,133),
2522            "violetred"=>array(208,32,144),
2523            "magenta"=>array(255,0,255),
2524            "violet"=>array(238,130,238),
2525            "plum"=>array(221,160,221),
2526            "orchid"=>array(218,112,214),
2527            "mediumorchid"=>array(186,85,211),
2528            "darkorchid"=>array(153,50,204),
2529            "darkviolet"=>array(148,0,211),
2530            "blueviolet"=>array(138,43,226),
2531            "purple"=>array(160,32,240),
2532            "mediumpurple"=>array(147,112,219),
2533            "thistle"=>array(216,191,216),
2534            "snow1"=>array(255,250,250),
2535            "snow2"=>array(238,233,233),
2536            "snow3"=>array(205,201,201),
2537            "snow4"=>array(139,137,137),
2538            "seashell1"=>array(255,245,238),
2539            "seashell2"=>array(238,229,222),
2540            "seashell3"=>array(205,197,191),
2541            "seashell4"=>array(139,134,130),
2542            "AntiqueWhite1"=>array(255,239,219),
2543            "AntiqueWhite2"=>array(238,223,204),
2544            "AntiqueWhite3"=>array(205,192,176),
2545            "AntiqueWhite4"=>array(139,131,120),
2546            "bisque1"=>array(255,228,196),
2547            "bisque2"=>array(238,213,183),
2548            "bisque3"=>array(205,183,158),
2549            "bisque4"=>array(139,125,107),
2550            "peachPuff1"=>array(255,218,185),
2551            "peachpuff2"=>array(238,203,173),
2552            "peachpuff3"=>array(205,175,149),
2553            "peachpuff4"=>array(139,119,101),
2554            "navajowhite1"=>array(255,222,173),
2555            "navajowhite2"=>array(238,207,161),
2556            "navajowhite3"=>array(205,179,139),
2557            "navajowhite4"=>array(139,121,94),
2558            "lemonchiffon1"=>array(255,250,205),
2559            "lemonchiffon2"=>array(238,233,191),
2560            "lemonchiffon3"=>array(205,201,165),
2561            "lemonchiffon4"=>array(139,137,112),
2562            "ivory1"=>array(255,255,240),
2563            "ivory2"=>array(238,238,224),
2564            "ivory3"=>array(205,205,193),
2565            "ivory4"=>array(139,139,131),
2566            "honeydew"=>array(193,205,193),
2567            "lavenderblush1"=>array(255,240,245),
2568            "lavenderblush2"=>array(238,224,229),
2569            "lavenderblush3"=>array(205,193,197),
2570            "lavenderblush4"=>array(139,131,134),
2571            "mistyrose1"=>array(255,228,225),
2572            "mistyrose2"=>array(238,213,210),
2573            "mistyrose3"=>array(205,183,181),
2574            "mistyrose4"=>array(139,125,123),
2575            "azure1"=>array(240,255,255),
2576            "azure2"=>array(224,238,238),
2577            "azure3"=>array(193,205,205),
2578            "azure4"=>array(131,139,139),
2579            "slateblue1"=>array(131,111,255),
2580            "slateblue2"=>array(122,103,238),
2581            "slateblue3"=>array(105,89,205),
2582            "slateblue4"=>array(71,60,139),
2583            "royalblue1"=>array(72,118,255),
2584            "royalblue2"=>array(67,110,238),
2585            "royalblue3"=>array(58,95,205),
2586            "royalblue4"=>array(39,64,139),
2587            "dodgerblue1"=>array(30,144,255),
2588            "dodgerblue2"=>array(28,134,238),
2589            "dodgerblue3"=>array(24,116,205),
2590            "dodgerblue4"=>array(16,78,139),
2591            "steelblue1"=>array(99,184,255),
2592            "steelblue2"=>array(92,172,238),
2593            "steelblue3"=>array(79,148,205),
2594            "steelblue4"=>array(54,100,139),
2595            "deepskyblue1"=>array(0,191,255),
2596            "deepskyblue2"=>array(0,178,238),
2597            "deepskyblue3"=>array(0,154,205),
2598            "deepskyblue4"=>array(0,104,139),
2599            "skyblue1"=>array(135,206,255),
2600            "skyblue2"=>array(126,192,238),
2601            "skyblue3"=>array(108,166,205),
2602            "skyblue4"=>array(74,112,139),
2603            "lightskyblue1"=>array(176,226,255),
2604            "lightskyblue2"=>array(164,211,238),
2605            "lightskyblue3"=>array(141,182,205),
2606            "lightskyblue4"=>array(96,123,139),
2607            "slategray1"=>array(198,226,255),
2608            "slategray2"=>array(185,211,238),
2609            "slategray3"=>array(159,182,205),
2610            "slategray4"=>array(108,123,139),
2611            "lightsteelblue1"=>array(202,225,255),
2612            "lightsteelblue2"=>array(188,210,238),
2613            "lightsteelblue3"=>array(162,181,205),
2614            "lightsteelblue4"=>array(110,123,139),
2615            "lightblue1"=>array(191,239,255),
2616            "lightblue2"=>array(178,223,238),
2617            "lightblue3"=>array(154,192,205),
2618            "lightblue4"=>array(104,131,139),
2619            "lightcyan1"=>array(224,255,255),
2620            "lightcyan2"=>array(209,238,238),
2621            "lightcyan3"=>array(180,205,205),
2622            "lightcyan4"=>array(122,139,139),
2623            "paleturquoise1"=>array(187,255,255),
2624            "paleturquoise2"=>array(174,238,238),
2625            "paleturquoise3"=>array(150,205,205),
2626            "paleturquoise4"=>array(102,139,139),
2627            "cadetblue1"=>array(152,245,255),
2628            "cadetblue2"=>array(142,229,238),
2629            "cadetblue3"=>array(122,197,205),
2630            "cadetblue4"=>array(83,134,139),
2631            "turquoise1"=>array(0,245,255),
2632            "turquoise2"=>array(0,229,238),
2633            "turquoise3"=>array(0,197,205),
2634            "turquoise4"=>array(0,134,139),
2635            "cyan1"=>array(0,255,255),
2636            "cyan2"=>array(0,238,238),
2637            "cyan3"=>array(0,205,205),
2638            "cyan4"=>array(0,139,139),
2639            "darkslategray1"=>array(151,255,255),
2640            "darkslategray2"=>array(141,238,238),
2641            "darkslategray3"=>array(121,205,205),
2642            "darkslategray4"=>array(82,139,139),
2643            "aquamarine1"=>array(127,255,212),
2644            "aquamarine2"=>array(118,238,198),
2645            "aquamarine3"=>array(102,205,170),
2646            "aquamarine4"=>array(69,139,116),
2647            "darkseagreen1"=>array(193,255,193),
2648            "darkseagreen2"=>array(180,238,180),
2649            "darkseagreen3"=>array(155,205,155),
2650            "darkseagreen4"=>array(105,139,105),
2651            "seagreen1"=>array(84,255,159),
2652            "seagreen2"=>array(78,238,148),
2653            "seagreen3"=>array(67,205,128),
2654            "seagreen4"=>array(46,139,87),
2655            "palegreen1"=>array(154,255,154),
2656            "palegreen2"=>array(144,238,144),
2657            "palegreen3"=>array(124,205,124),
2658            "palegreen4"=>array(84,139,84),
2659            "springgreen1"=>array(0,255,127),
2660            "springgreen2"=>array(0,238,118),
2661            "springgreen3"=>array(0,205,102),
2662            "springgreen4"=>array(0,139,69),
2663            "chartreuse1"=>array(127,255,0),
2664            "chartreuse2"=>array(118,238,0),
2665            "chartreuse3"=>array(102,205,0),
2666            "chartreuse4"=>array(69,139,0),
2667            "olivedrab1"=>array(192,255,62),
2668            "olivedrab2"=>array(179,238,58),
2669            "olivedrab3"=>array(154,205,50),
2670            "olivedrab4"=>array(105,139,34),
2671            "darkolivegreen1"=>array(202,255,112),
2672            "darkolivegreen2"=>array(188,238,104),
2673            "darkolivegreen3"=>array(162,205,90),
2674            "darkolivegreen4"=>array(110,139,61),
2675            "khaki1"=>array(255,246,143),
2676            "khaki2"=>array(238,230,133),
2677            "khaki3"=>array(205,198,115),
2678            "khaki4"=>array(139,134,78),
2679            "lightgoldenrod1"=>array(255,236,139),
2680            "lightgoldenrod2"=>array(238,220,130),
2681            "lightgoldenrod3"=>array(205,190,112),
2682            "lightgoldenrod4"=>array(139,129,76),
2683            "yellow1"=>array(255,255,0),
2684            "yellow2"=>array(238,238,0),
2685            "yellow3"=>array(205,205,0),
2686            "yellow4"=>array(139,139,0),
2687            "gold1"=>array(255,215,0),
2688            "gold2"=>array(238,201,0),
2689            "gold3"=>array(205,173,0),
2690            "gold4"=>array(139,117,0),
2691            "goldenrod1"=>array(255,193,37),
2692            "goldenrod2"=>array(238,180,34),
2693            "goldenrod3"=>array(205,155,29),
2694            "goldenrod4"=>array(139,105,20),
2695            "darkgoldenrod1"=>array(255,185,15),
2696            "darkgoldenrod2"=>array(238,173,14),
2697            "darkgoldenrod3"=>array(205,149,12),
2698            "darkgoldenrod4"=>array(139,101,8),
2699            "rosybrown1"=>array(255,193,193),
2700            "rosybrown2"=>array(238,180,180),
2701            "rosybrown3"=>array(205,155,155),
2702            "rosybrown4"=>array(139,105,105),
2703            "indianred1"=>array(255,106,106),
2704            "indianred2"=>array(238,99,99),
2705            "indianred3"=>array(205,85,85),
2706            "indianred4"=>array(139,58,58),
2707            "sienna1"=>array(255,130,71),
2708            "sienna2"=>array(238,121,66),
2709            "sienna3"=>array(205,104,57),
2710            "sienna4"=>array(139,71,38),
2711            "burlywood1"=>array(255,211,155),
2712            "burlywood2"=>array(238,197,145),
2713            "burlywood3"=>array(205,170,125),
2714            "burlywood4"=>array(139,115,85),
2715            "wheat1"=>array(255,231,186),
2716            "wheat2"=>array(238,216,174),
2717            "wheat3"=>array(205,186,150),
2718            "wheat4"=>array(139,126,102),
2719            "tan1"=>array(255,165,79),
2720            "tan2"=>array(238,154,73),
2721            "tan3"=>array(205,133,63),
2722            "tan4"=>array(139,90,43),
2723            "chocolate1"=>array(255,127,36),
2724            "chocolate2"=>array(238,118,33),
2725            "chocolate3"=>array(205,102,29),
2726            "chocolate4"=>array(139,69,19),
2727            "firebrick1"=>array(255,48,48),
2728            "firebrick2"=>array(238,44,44),
2729            "firebrick3"=>array(205,38,38),
2730            "firebrick4"=>array(139,26,26),
2731            "brown1"=>array(255,64,64),
2732            "brown2"=>array(238,59,59),
2733            "brown3"=>array(205,51,51),
2734            "brown4"=>array(139,35,35),
2735            "salmon1"=>array(255,140,105),
2736            "salmon2"=>array(238,130,98),
2737            "salmon3"=>array(205,112,84),
2738            "salmon4"=>array(139,76,57),
2739            "lightsalmon1"=>array(255,160,122),
2740            "lightsalmon2"=>array(238,149,114),
2741            "lightsalmon3"=>array(205,129,98),
2742            "lightsalmon4"=>array(139,87,66),
2743            "orange1"=>array(255,165,0),
2744            "orange2"=>array(238,154,0),
2745            "orange3"=>array(205,133,0),
2746            "orange4"=>array(139,90,0),
2747            "darkorange1"=>array(255,127,0),
2748            "darkorange2"=>array(238,118,0),
2749            "darkorange3"=>array(205,102,0),
2750            "darkorange4"=>array(139,69,0),
2751            "coral1"=>array(255,114,86),
2752            "coral2"=>array(238,106,80),
2753            "coral3"=>array(205,91,69),
2754            "coral4"=>array(139,62,47),
2755            "tomato1"=>array(255,99,71),
2756            "tomato2"=>array(238,92,66),
2757            "tomato3"=>array(205,79,57),
2758            "tomato4"=>array(139,54,38),
2759            "orangered1"=>array(255,69,0),
2760            "orangered2"=>array(238,64,0),
2761            "orangered3"=>array(205,55,0),
2762            "orangered4"=>array(139,37,0),
2763            "deeppink1"=>array(255,20,147),
2764            "deeppink2"=>array(238,18,137),
2765            "deeppink3"=>array(205,16,118),
2766            "deeppink4"=>array(139,10,80),
2767            "hotpink1"=>array(255,110,180),
2768            "hotpink2"=>array(238,106,167),
2769            "hotpink3"=>array(205,96,144),
2770            "hotpink4"=>array(139,58,98),
2771            "pink1"=>array(255,181,197),
2772            "pink2"=>array(238,169,184),
2773            "pink3"=>array(205,145,158),
2774            "pink4"=>array(139,99,108),
2775            "lightpink1"=>array(255,174,185),
2776            "lightpink2"=>array(238,162,173),
2777            "lightpink3"=>array(205,140,149),
2778            "lightpink4"=>array(139,95,101),
2779            "palevioletred1"=>array(255,130,171),
2780            "palevioletred2"=>array(238,121,159),
2781            "palevioletred3"=>array(205,104,137),
2782            "palevioletred4"=>array(139,71,93),
2783            "maroon1"=>array(255,52,179),
2784            "maroon2"=>array(238,48,167),
2785            "maroon3"=>array(205,41,144),
2786            "maroon4"=>array(139,28,98),
2787            "violetred1"=>array(255,62,150),
2788            "violetred2"=>array(238,58,140),
2789            "violetred3"=>array(205,50,120),
2790            "violetred4"=>array(139,34,82),
2791            "magenta1"=>array(255,0,255),
2792            "magenta2"=>array(238,0,238),
2793            "magenta3"=>array(205,0,205),
2794            "magenta4"=>array(139,0,139),
2795            "mediumred"=>array(140,34,34),         
2796            "orchid1"=>array(255,131,250),
2797            "orchid2"=>array(238,122,233),
2798            "orchid3"=>array(205,105,201),
2799            "orchid4"=>array(139,71,137),
2800            "plum1"=>array(255,187,255),
2801            "plum2"=>array(238,174,238),
2802            "plum3"=>array(205,150,205),
2803            "plum4"=>array(139,102,139),
2804            "mediumorchid1"=>array(224,102,255),
2805            "mediumorchid2"=>array(209,95,238),
2806            "mediumorchid3"=>array(180,82,205),
2807            "mediumorchid4"=>array(122,55,139),
2808            "darkorchid1"=>array(191,62,255),
2809            "darkorchid2"=>array(178,58,238),
2810            "darkorchid3"=>array(154,50,205),
2811            "darkorchid4"=>array(104,34,139),
2812            "purple1"=>array(155,48,255),
2813            "purple2"=>array(145,44,238),
2814            "purple3"=>array(125,38,205),
2815            "purple4"=>array(85,26,139),
2816            "mediumpurple1"=>array(171,130,255),
2817            "mediumpurple2"=>array(159,121,238),
2818            "mediumpurple3"=>array(137,104,205),
2819            "mediumpurple4"=>array(93,71,139),
2820            "thistle1"=>array(255,225,255),
2821            "thistle2"=>array(238,210,238),
2822            "thistle3"=>array(205,181,205),
2823            "thistle4"=>array(139,123,139),
2824            "gray1"=>array(10,10,10),
2825            "gray2"=>array(40,40,30),
2826            "gray3"=>array(70,70,70),
2827            "gray4"=>array(100,100,100),
2828            "gray5"=>array(130,130,130),
2829            "gray6"=>array(160,160,160),
2830            "gray7"=>array(190,190,190),
2831            "gray8"=>array(210,210,210),
2832            "gray9"=>array(240,240,240),
2833            "darkgray"=>array(100,100,100),
2834            "darkblue"=>array(0,0,139),
2835            "darkcyan"=>array(0,139,139),
2836            "darkmagenta"=>array(139,0,139),
2837            "darkred"=>array(139,0,0),
2838            "silver"=>array(192, 192, 192),
2839            "eggplant"=>array(144,176,168),
2840            "lightgreen"=>array(144,238,144));         
2841    }
2842//----------------
2843// PUBLIC METHODS
2844    // Colors can be specified as either
2845    // 1. #xxxxxx                       HTML style
2846    // 2. "colorname"   as a named color
2847    // 3. array(r,g,b)  RGB triple
2848    // This function translates this to a native RGB format and returns an
2849    // RGB triple.
2850    function Color($aColor) {
2851        if (is_string($aColor)) {
2852
2853           // Extract potential adjustment figure at end of color
2854           // specification
2855            $aColor = strtok($aColor,":");
2856            $adj = 0+strtok(":");
2857            if( $adj==0 ) $adj=1;
2858            if (substr($aColor, 0, 1) == "#") {
2859                return array($adj*hexdec(substr($aColor, 1, 2)),
2860                             $adj*hexdec(substr($aColor, 3, 2)),
2861                             $adj*hexdec(substr($aColor, 5, 2)));
2862            } else {
2863                if(!isset($this->rgb_table[$aColor]) )
2864                    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown color: <strong>$aColor</strong>");
2865                $tmp=$this->rgb_table[$aColor];
2866                return array($adj*$tmp[0],$adj*$tmp[1],$adj*$tmp[2]);
2867            }
2868        } elseif( is_array($aColor) && (count($aColor)==3) ) {
2869            return $aColor;
2870        }
2871        else
2872            JpGraphError::Raise("<b>JpGraph Error:</b> Unknown color specification: $aColor , size=".count($aColor));
2873    }
2874       
2875    // Compare two colors
2876    // return true if equal
2877    function Equal($aCol1,$aCol2) {
2878        $c1 = $this->Color($aCol1);
2879        $c2 = $this->Color($aCol2);
2880        if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
2881            return true;
2882        else
2883            return false;
2884    }
2885       
2886    // Allocate a new color in the current image
2887    // Return new color index, -1 if no more colors could be allocated
2888    function Allocate($aColor) {
2889        list ($r, $g, $b) = $this->color($aColor);
2890        if($GLOBALS['gd2']==true) {
2891            return imagecolorresolvealpha($this->img, $r, $g, $b, 0);
2892        } else {
2893            $index = imagecolorexact($this->img, $r, $g, $b);
2894            if ($index == -1) {
2895                $index = imagecolorallocate($this->img, $r, $g, $b);
2896                if( USE_APPROX_COLORS && $index == -1 )
2897                    $index = imagecolorresolve($this->img, $r, $g, $b);
2898            }
2899            return $index;
2900        }
2901    }
2902} // Class
2903
2904       
2905//===================================================
2906// CLASS Image
2907// Description: Wrapper class with some goodies to form the
2908// Interface to low level image drawing routines.
2909//===================================================
2910class Image {
2911    var $img_format;
2912    var $expired=false;
2913    var $img;
2914    var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
2915    var $plotwidth,$plotheight;
2916    var $rgb;
2917    var $current_color,$current_color_name;
2918    var $lastx=0, $lasty=0;
2919    var $width, $height;
2920    var $line_weight=1;
2921    var $line_style=1;  // Default line style is solid
2922    var $obs_list=array();
2923    var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
2924    var $text_halign="left",$text_valign="bottom";
2925    var $ttf=null;
2926    var $use_anti_aliasing=false;
2927    var $quality=null;
2928    var $colorstack=array(),$colorstackidx=0;
2929    //---------------
2930    // CONSTRUCTOR
2931    function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
2932        $this->CreateImgCanvas($aWidth,$aHeight);
2933        if( !$this->SetImgFormat($aFormat) ) {
2934            JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
2935        }
2936        $this->ttf = new TTF();
2937    }
2938       
2939    function SetAutoMargin() { 
2940        $min_bm=10;
2941        if( BRAND_TIMING )
2942            $min_bm=15;         
2943        $lm = max(12,$this->width/7);
2944        $rm = max(12,$this->width/10);
2945        $tm = max(24,$this->height/7);
2946        $bm = max($min_bm,$this->height/7);
2947        $this->SetMargin($lm,$rm,$tm,$bm);             
2948    }
2949       
2950    function CreateImgCanvas($aWidth=-1,$aHeight=-1) {
2951        $this->width=$aWidth;
2952        $this->height=$aHeight;         
2953
2954        $this->SetAutoMargin();
2955
2956        if( $aWidth==-1 || $aHeight==-1 ) {
2957            // We will set the final size later.
2958            // Note: The size must be specified before any other
2959            // img routines that stroke anything are called.
2960            $this->img = null;
2961            $this->rgb = null;
2962            return;
2963        }
2964               
2965        if( $GLOBALS['gd2']==true && USE_TRUECOLOR ) {
2966            $this->img = imagecreatetruecolor($aWidth, $aHeight);       
2967            imagefilledrectangle($this->img, 0, 0, $aWidth, $aHeight, 0xffffff);
2968        } else {
2969            $this->img = imagecreate($aWidth, $aHeight);       
2970        }               
2971        assert($this->img != 0);               
2972        $this->rgb = new RGB($this->img);                               
2973               
2974        // First index is background so this will be white
2975        $this->SetColor("white");
2976    }
2977       
2978                               
2979    //---------------
2980    // PUBLIC METHODS   
2981
2982    // Add observer. The observer will be notified when
2983    // the margin changes
2984    function AddObserver($aMethod,&$aObject) {
2985        $this->obs_list[]=array($aMethod,&$aObject);
2986    }
2987       
2988    // Call all observers
2989    function NotifyObservers() {
2990        //      foreach($this->obs_list as $o)
2991        //              $o[1]->$o[0]($this);
2992        for($i=0; $i < count($this->obs_list); ++$i) {
2993            $obj = & $this->obs_list[$i][1];
2994            $method = $this->obs_list[$i][0];
2995            $obj->$method($this);
2996        }
2997    }   
2998       
2999    function SetFont($family,$style=FS_NORMAL,$size=10) {
3000        if($family==FONT1_BOLD || $family==FONT2_BOLD || $family==FONT0 || $family==FONT1 || $family==FONT2 )
3001            JpGraphError::Raise("<b>JpGraph Error:</b> Usage of FONT0, FONT1, FONT2 is deprecated. Use FF_xxx instead.");
3002               
3003        $this->font_family=$family;
3004        $this->font_style=$style;
3005        $this->font_size=$size;
3006        if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
3007            ++$this->font_family;
3008        }
3009    }
3010       
3011    // Get the specific height for a text string
3012    function GetTextHeight($txt="",$angle=0) {
3013        // Builtin font?
3014        $tmp = split("\n",$txt);
3015        $n = count($tmp);
3016        $m=0;
3017        for($i=0; $i<count($tmp); ++$i)
3018            $m = max($m,strlen($tmp[$i]));
3019
3020        if( $this->font_family <= FF_FONT2+1 ) {
3021            if( $angle==0 )
3022                return $n*imagefontheight($this->font_family);
3023            else
3024                return $m*imagefontwidth($this->font_family);
3025        }
3026        else {
3027            $file = $this->ttf->File($this->font_family,$this->font_style);                     
3028            $bbox = ImageTTFBBox($this->font_size,$angle,$file,"XXOOMM"/*$txt*/);
3029            return $n*(abs($bbox[5])+abs($bbox[1])); // upper_right_y - lower_left_y                   
3030        }
3031    }
3032       
3033    // Estimate font height
3034    function GetFontHeight($txt="XMg",$angle=0) {
3035        $tmp = split("\n",$txt);
3036        return $this->GetTextHeight($tmp[0],$angle);
3037    }
3038       
3039    // Approximate font width with width of letter "O"
3040    function GetFontWidth($txt="O",$angle=0) {
3041        return $this->GetTextWidth($txt,$angle);
3042    }
3043       
3044    // Get actual width of text in absolute pixels
3045    function GetTextWidth($txt,$angle=0) {
3046        // Builtin font?
3047        $tmp = split("\n",$txt);
3048        $n = count($tmp);
3049        $m=0;
3050        for($i=0; $i<count($tmp); ++$i)
3051            $m = max($m,strlen($tmp[$i]));
3052
3053        if( $this->font_family <= FF_FONT2+1 ) {
3054            if( $angle==0 ) {
3055                $width=$m*imagefontwidth($this->font_family);
3056                return $width;
3057            }
3058            else
3059                return $n*imagefontheight($this->font_family); // 90 degrees internal so height become width
3060        }
3061        else {
3062            $file = $this->ttf->File($this->font_family,$this->font_style);                     
3063            $bbox = ImageTTFBBox($this->font_size,$angle,$file,$txt);
3064            return $n*(abs($bbox[2]-$bbox[6]));
3065        }
3066    }
3067       
3068    // Draw text with a box around it
3069    function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
3070    $shadow=false,$paragraph_align="left") {
3071
3072        if( !is_numeric($dir) ) {
3073            if( $dir=="h" ) $dir=0;
3074            elseif( $dir=="v" ) $dir=90;
3075            else JpGraphError::Raise("<b>JpGraph Error:</b> Unknown direction specified in call to StrokeBoxedText() [$dir]");
3076        }
3077               
3078        $width=$this->GetTextWidth($txt,$dir);
3079        $height=$this->GetTextHeight($txt,$dir);
3080
3081        if( $this->font_family<=FF_FONT2+1 ) {
3082            $xmarg=3;   
3083            $ymarg=3;
3084        }
3085        else {
3086            $xmarg=6;   
3087            $ymarg=6;
3088        }               
3089        $height += 2*$ymarg;
3090        $width += 2*$xmarg;
3091        if( $this->text_halign=="right" ) $x -= $width;
3092        elseif( $this->text_halign=="center" ) $x -= $width/2;
3093        if( $this->text_valign=="bottom" ) $y -= $height;
3094        elseif( $this->text_valign=="center" ) $y -= $height/2;
3095       
3096        if( $shadow ) {
3097            $oc=$this->current_color;
3098            $this->SetColor($bcolor);
3099            $this->ShadowRectangle($x,$y,$x+$width+2,$y+$height+2,$fcolor,2);
3100            $this->current_color=$oc;
3101        }
3102        else {
3103            if( $fcolor ) {
3104                $oc=$this->current_color;
3105                $this->SetColor($fcolor);
3106                $this->FilledRectangle($x,$y,$x+$width,$y+$height);
3107                $this->current_color=$oc;
3108            }
3109            if( $bcolor ) {
3110                $oc=$this->current_color;
3111                $this->SetColor($bcolor);                       
3112                $this->Rectangle($x,$y,$x+$width,$y+$height);
3113                $this->current_color=$oc;                       
3114            }
3115        }
3116               
3117        $h=$this->text_halign;
3118        $v=$this->text_valign;
3119        $this->SetTextAlign("left","top");
3120        $this->StrokeText($x+$xmarg, $y+$ymarg, $txt, $dir, $paragraph_align);
3121        $this->SetTextAlign($h,$v);
3122    }
3123
3124    // Set text alignment       
3125    function SetTextAlign($halign,$valign="bottom") {
3126        $this->text_halign=$halign;
3127        $this->text_valign=$valign;
3128    }
3129       
3130    // Should we use anti-aliasing. Note: This really slows down graphics!
3131    function SetAntiAliasing() {
3132        $this->use_anti_aliasing=true;
3133    }
3134       
3135    function StrokeText($x,$y,$txt,$dir=0,$paragraph_align) {
3136
3137        // Do special language encoding
3138        if( LANGUAGE_CYRILLIC )
3139            $txt = LanguageConv::ToCyrillic($txt);
3140
3141        if( !is_numeric($dir) )
3142            JpGraphError::Raise("<b>JpGraph Error:</b> Direction for text most be given as an angle between 0 and 90.");
3143                       
3144        if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {       // Internal font
3145            if( is_numeric($dir) && $dir!=90 && $dir!=0)
3146                JpGraphError::Raise("<b>JpGraph Error:</b> Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
3147
3148            $h=$this->GetTextHeight($txt);
3149            $fh=$this->GetFontHeight($txt);
3150            $w=$this->GetTextWidth($txt);
3151
3152            if( $this->text_halign=="right")                           
3153                $x -= $dir==0 ? $w : $h;
3154            elseif( $this->text_halign=="center" )
3155                $x -= $dir==0 ? $w/2 : $h/2;
3156                               
3157            if( $this->text_valign=="top" )
3158                $y +=   $dir==0 ? $h : $w;
3159            elseif( $this->text_valign=="center" )                             
3160                $y +=   $dir==0 ? $h/2 : $w/2;
3161                               
3162            if( $dir==90 )
3163                imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
3164            else        {
3165                if (ereg("\n",$txt)) {
3166                    $tmp = split("\n",$txt);
3167                    for($i=0; $i<count($tmp); ++$i) {
3168                        $w1 = $this->GetTextWidth($tmp[$i]);
3169                        if( $paragraph_align=="left" ) {
3170                            imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
3171                        }
3172                        elseif( $paragraph_align=="right" ) {
3173                            imagestring($this->img,$this->font_family,$x+($w-$w1),
3174                            $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
3175                        }
3176                        else {
3177                            imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
3178                            $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
3179                        }
3180                    }
3181                }else{
3182                    //Put the text
3183                    imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
3184                }
3185            }
3186        }
3187        elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_VERA)  { // TTF font
3188            $file = $this->ttf->File($this->font_family,$this->font_style);                     
3189            $angle=$dir;
3190            $bbox=ImageTTFBBox($this->font_size,$angle,$file,$txt);
3191            if( $this->text_halign=="right" ) $x -= $bbox[2]-$bbox[0];
3192            elseif( $this->text_halign=="center" )      $x -= ($bbox[4]-$bbox[0])/2;
3193            elseif( $this->text_halign=="topanchor" ) $x -= $bbox[4]-$bbox[0];
3194            elseif( $this->text_halign=="left" ) $x += -($bbox[6]-$bbox[0]);
3195                       
3196            if( $this->text_valign=="top" ) $y -= $bbox[5];
3197            elseif( $this->text_valign=="center" )      $y -= ($bbox[5]-$bbox[1])/2;
3198            elseif( $this->text_valign=="bottom" ) $y -= $bbox[1];
3199                               
3200            // Use lower left of bbox as fix-point, not the default baselinepoint.                             
3201            $x -= $bbox[0];
3202            if($GLOBALS['gd2']) {
3203                $old = ImageAlphaBlending($this->img, true);
3204            }
3205            ImageTTFText ($this->img, $this->font_size, $angle, $x, $y,
3206            $this->current_color,$file,$txt);
3207            if($GLOBALS['gd2']) {
3208                ImageAlphaBlending($this->img, $old);
3209            }
3210        }
3211        else
3212            JpGraphError::Raise("<b>JpGraph Error:</b> Unknown font font family specification. ");
3213    }
3214       
3215    function SetMargin($lm,$rm,$tm,$bm) {
3216        $this->left_margin=$lm;
3217        $this->right_margin=$rm;
3218        $this->top_margin=$tm;
3219        $this->bottom_margin=$bm;
3220        $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ;
3221        $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;     
3222        $this->NotifyObservers();
3223    }
3224
3225    function SetTransparent($color) {
3226        imagecolortransparent ($this->img,$this->rgb->allocate($color));
3227    }
3228       
3229    function SetColor($color) {
3230        $this->current_color_name = $color;
3231        $this->current_color=$this->rgb->allocate($color);
3232        if( $this->current_color == -1 ) {
3233            $tc=imagecolorstotal($this->img);
3234            JpGraphError::Raise("<b><b>JpGraph Error:</b> Can't allocate any more colors.</b><br>
3235                                Image has already allocated maximum of <b>$tc colors</b>.
3236                                This might happen if you have anti-aliasing turned on
3237                                together with a background image or perhaps gradient fill
3238                                since this requires many, many colors. Try to turn off
3239                                anti-aliasing.<p>
3240                                If there is still a problem try downgrading the quality of
3241                                the background image to use a smaller pallete to leave some
3242                                entries for your graphs. You should try to limit the number
3243                                of colors in your background image to 64.<p>
3244                                If there is still problem set the constant
3245<pre>
3246DEFINE(\"USE_APPROX_COLORS\",true);
3247</pre>
3248                                in jpgraph.php This will use approximative colors
3249                                when the palette is full.
3250                                <p>
3251                                Unfortunately there is not much JpGraph can do about this
3252                                since the palette size is a limitation of current graphic format and
3253                                what the underlying GD library suppports.");
3254        }
3255        return $this->current_color;
3256    }
3257       
3258    function PushColor($color) {
3259        if( $color != "" ) {
3260            $this->colorstack[$this->colorstackidx]=$this->current_color_name;
3261            $this->colorstack[$this->colorstackidx+1]=$this->current_color;
3262            $this->colorstackidx+=2;
3263            $this->SetColor($color);
3264        }
3265    }
3266       
3267    function PopColor() {
3268        if($this->colorstackidx<0)
3269            JpGraphError::Raise("<b>JpGraph Error:</b> Negative Color stack index. Unmatched call to PopColor()");
3270        $this->current_color=$this->colorstack[--$this->colorstackidx];
3271        $this->current_color_name=$this->colorstack[--$this->colorstackidx];
3272    }
3273       
3274       
3275    // Why this duplication? Because this way we can call this method
3276    // for any image and not only the current objsct
3277    function AdjSat($sat) {     $this->_AdjSat($this->img,$sat);        }       
3278       
3279    function _AdjSat($img,$sat) {
3280        $nbr = imagecolorstotal ($img);
3281        for( $i=0; $i<$nbr; ++$i ) {
3282            $colarr = imagecolorsforindex ($img,$i);
3283            $rgb[0]=$colarr["red"];
3284            $rgb[1]=$colarr["green"];
3285            $rgb[2]=$colarr["blue"];
3286            $rgb = $this->AdjRGBSat($rgb,$sat);
3287            imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
3288        }
3289    }
3290       
3291    function AdjBrightContrast($bright,$contr=0) {
3292        $this->_AdjBrightContrast($this->img,$bright,$contr);
3293    }
3294    function _AdjBrightContrast($img,$bright,$contr=0) {
3295        if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
3296            JpGraphError::Raise("<b>JpGraph Error:</b> Parameters for brightness and Contrast out of range [-1,1]");           
3297        $nbr = imagecolorstotal ($img);
3298        for( $i=0; $i<$nbr; ++$i ) {
3299            $colarr = imagecolorsforindex ($img,$i);
3300            $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
3301            $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
3302            $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);           
3303            imagecolorset ($img, $i, $r, $g, $b);
3304        }
3305    }
3306       
3307    // Private helper function for adj sat
3308    // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
3309    // Note: Due to GD inability to handle true color the RGB values are only between
3310    // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
3311    //
3312    // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
3313    // to it's complement.
3314    //
3315    // Implementation note: The saturation is implemented directly in the RGB space
3316    // by adjusting the perpendicular distance between the RGB point and the "grey"
3317    // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
3318    // distance and a negative value moves the point closer to the line.
3319    // The values are truncated when the color point hits the bounding box along the
3320    // RGB axis.
3321    // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color
3322    // saturation function in RGB space. However, it looks ok and has the expected effect.
3323    function AdjRGBSat($rgb,$sat) {
3324        // TODO: Should be moved to the RGB class
3325        // Grey vector
3326        $v=array(1,1,1);
3327
3328        // Dot product
3329        $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
3330
3331        // Normalize dot product
3332        $normdot = $dot/3;      // dot/|v|^2
3333
3334        // Direction vector between $u and its projection onto $v
3335        for($i=0; $i<3; ++$i)
3336            $r[$i] = $rgb[$i] - $normdot*$v[$i];
3337
3338        // Adjustment factor so that sat==1 sets the highest RGB value to 255
3339        if( $sat > 0 ) {
3340            $m=0;
3341            for( $i=0; $i<3; ++$i) {
3342                if( sign($r[$i]) == 1 && $r[$i]>0)
3343                    $m=max($m,(255-$rgb[$i])/$r[$i]);
3344            }
3345            $tadj=$m;
3346        }
3347        else
3348            $tadj=1;
3349               
3350        $tadj = $tadj*$sat;     
3351        for($i=0; $i<3; ++$i) {
3352            $un[$i] = round($rgb[$i] + $tadj*$r[$i]);           
3353            if( $un[$i]<0 ) $un[$i]=0;          // Truncate color when they reach 0
3354            if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
3355        }               
3356        return $un;     
3357    }   
3358
3359    // Private helper function for AdjBrightContrast
3360    function AdjRGBBrightContrast($rgb,$bright,$contr) {
3361        // TODO: Should be moved to the RGB class
3362        // First handle contrast, i.e change the dynamic range around grey
3363        if( $contr <= 0 ) {
3364            // Decrease contrast
3365            $adj = abs($rgb-128) * (-$contr);
3366            if( $rgb < 128 ) $rgb += $adj;
3367            else $rgb -= $adj;
3368        }
3369        else { // $contr > 0
3370            // Increase contrast
3371            if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
3372            else $rgb = $rgb + ((255-$rgb) * $contr);
3373        }
3374       
3375        // Add (or remove) various amount of white
3376        $rgb += $bright*255;   
3377        $rgb=min($rgb,255);
3378        $rgb=max($rgb,0);
3379        return $rgb;   
3380    }
3381       
3382    function SetLineWeight($weight) {
3383        $this->line_weight = $weight;
3384    }
3385       
3386    function SetStartPoint($x,$y) {
3387        $this->lastx=$x;
3388        $this->lasty=$y;
3389    }
3390       
3391    function Arc($cx,$cy,$w,$h,$s,$e) {
3392        imagearc($this->img,$cx,$cy,$w,$h,$s,$e,$this->current_color);
3393    }
3394   
3395    function FilledArc($cx,$cy,$w,$h,$s,$e) {
3396        if( $GLOBALS['gd2'] == false ) {
3397            JpGraphError::Raise("<b>JpGraph Error:</b> This plot type requires GD 2.x or later");
3398            die();
3399        }
3400        imagefilledarc($this->img,$cx,$cy,$w,$h,$s,$e,$this->current_color,IMG_ARC_PIE);
3401    }
3402
3403    function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
3404        $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
3405    }
3406
3407    function CakeSlice($cx,$cy,$w,$h,$s,$e,$fillcolor="",$arccolor="",$stepping=2500) {
3408        // Calculate a polygon approximation
3409        $p[0] = $cx;
3410        $p[1] = $cy;
3411        // Heuristic on how many polygons we need to make the
3412        // arc look good
3413        $numsteps = round(8 * abs($e-$s) * ($w*$w+$h*$h)/$stepping);
3414        //echo "Numsteps=$numsteps<br>\n";
3415        if( $numsteps < 2 ) $numsteps=2;
3416        $delta = ($e-$s)/$numsteps;
3417        $pa=array();
3418        $a = $s;
3419        for($i=1; $i<=$numsteps; ++$i ) {
3420            $p[2*$i] = round($cx + $w*cos($a));
3421            $p[2*$i+1] = round($cy - $h*sin($a));
3422            $a = $s + $i*$delta;
3423            $pa[2*($i-1)] = $p[2*$i];
3424            $pa[2*($i-1)+1] = $p[2*$i+1];
3425        }
3426        // Get the last point at the exact ending angle to avoid
3427        // any rounding errors.
3428        $p[2*$i] = round($cx + $w*cos($e));
3429        $p[2*$i+1] = round($cy - $h*sin($e));
3430        $pa[2*($i-1)] = $p[2*$i];
3431        $pa[2*($i-1)+1] = $p[2*$i+1];
3432        $i++;
3433
3434        $p[2*$i] = $cx;
3435        $p[2*$i+1] = $cy;
3436        if( $fillcolor != "" ) {
3437            $this->PushColor($fillcolor);
3438            $this->FilledPolygon($p);
3439            $this->PopColor();
3440        }
3441        $this->Polygon($p);
3442        if( $arccolor != "" && ($arccolor != $this->current_color) ) {
3443            $n = count($pa)/2;
3444            $this->PushColor($arccolor);
3445            $this->SetStartPoint($pa[0],$pa[1]);
3446            for($i=1; $i<$n; ++$i)
3447                $this->LineTo($pa[2*$i],$pa[2*$i+1]);
3448            $this->PopColor();
3449        }
3450    }
3451
3452    function Ellipse($xc,$yc,$w,$h) {
3453        $this->Arc($xc,$yc,$w,$h,0,360);
3454    }
3455       
3456    // Breseham circle gives visually better result then using GD
3457    // built in arc(). It takes some more time but gives better
3458    // accuracy.
3459    function BresenhamCircle($xc,$yc,$r) {
3460        $d = 3-2*$r;
3461        $x = 0;
3462        $y = $r;
3463        while($x<=$y) {
3464            $this->Point($xc+$x,$yc+$y);                       
3465            $this->Point($xc+$x,$yc-$y);
3466            $this->Point($xc-$x,$yc+$y);
3467            $this->Point($xc-$x,$yc-$y);
3468                       
3469            $this->Point($xc+$y,$yc+$x);
3470            $this->Point($xc+$y,$yc-$x);
3471            $this->Point($xc-$y,$yc+$x);
3472            $this->Point($xc-$y,$yc-$x);
3473                       
3474            if( $d<0 ) $d += 4*$x+6;
3475            else {
3476                $d += 4*($x-$y)+10;             
3477                --$y;
3478            }
3479            ++$x;
3480        }
3481    }
3482                       
3483    function Circle($xc,$yc,$r) {
3484        if( USE_BRESENHAM )
3485            $this->BresenhamCircle($xc,$yc,$r);
3486        else {
3487            $this->Arc($xc,$yc,$r*2,$r*2,0,360);               
3488            // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
3489            //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
3490        }
3491    }
3492       
3493    function FilledCircle($xc,$yc,$r) {
3494        imagefilledellipse($this->img,$xc,$yc,2*$r,2*$r,$this->current_color);
3495    }
3496       
3497    // Linear Color InterPolation
3498    function lip($f,$t,$p) {
3499        $p = round($p,1);
3500        $r = $f[0] + ($t[0]-$f[0])*$p;
3501        $g = $f[1] + ($t[1]-$f[1])*$p;
3502        $b = $f[2] + ($t[2]-$f[2])*$p;
3503        return array($r,$g,$b);
3504    }
3505
3506    // Anti-aliased line.
3507    // Note that this is roughly 8 times slower then a normal line!
3508    function WuLine($x1,$y1,$x2,$y2) {
3509        // Get foreground line color
3510        $lc = imagecolorsforindex($this->img,$this->current_color);
3511        $lc = array($lc["red"],$lc["green"],$lc["blue"]);
3512
3513        $dx = $x2-$x1;
3514        $dy = $y2-$y1;
3515       
3516        if( abs($dx) > abs($dy) ) {
3517            if( $dx<0 ) {
3518                $dx = -$dx;$dy = -$dy;
3519                $tmp=$x2;$x2=$x1;$x1=$tmp;
3520                $tmp=$y2;$y2=$y1;$y1=$tmp;
3521            }
3522            $x=$x1<<16; $y=$y1<<16;
3523            $yinc = ($dy*65535)/$dx;
3524            while( ($x >> 16) < $x2 ) {
3525                               
3526                $bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
3527                $bc=array($bc["red"],$bc["green"],$bc["blue"]);
3528                               
3529                $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
3530                imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
3531                $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
3532                imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
3533                $x += 65536; $y += $yinc;
3534            }
3535        }
3536        else {
3537            if( $dy<0 ) {
3538                $dx = -$dx;$dy = -$dy;
3539                $tmp=$x2;$x2=$x1;$x1=$tmp;
3540                $tmp=$y2;$y2=$y1;$y1=$tmp;
3541            }
3542            $x=$x1<<16; $y=$y1<<16;
3543            $xinc = ($dx*65535)/$dy;   
3544            while( ($y >> 16) < $y2 ) {
3545                               
3546                $bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
3547                $bc=array($bc["red"],$bc["green"],$bc["blue"]);                         
3548                               
3549                $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
3550                imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
3551                $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
3552                imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
3553                $y += 65536; $x += $xinc;
3554            }
3555        }
3556        $this->SetColor($lc);
3557        imagesetpixel($this->img,$x2,$y2,$this->current_color);         
3558        imagesetpixel($this->img,$x1,$y1,$this->current_color);                 
3559    }
3560
3561    // Set line style dashed, dotted etc
3562    function SetLineStyle($s) {
3563        if( is_numeric($s) ) {
3564            if( $s<1 || $s>4 )
3565                JpGraphError::Raise("<b>JpGraph Error:</b> Illegal numeric argument to SetLineStyle(): $s");
3566        }
3567        elseif( is_string($s) ) {
3568            if( $s == "solid" ) $s=1;
3569            elseif( $s == "dotted" ) $s=2;
3570            elseif( $s == "dashed" ) $s=3;
3571            elseif( $s == "longdashed" ) $s=4;
3572            else JpGraphError::Raise("<b>JpGraph Error:</b> Illegal string argument to SetLineStyle(): $s");
3573        }
3574        else JpGraphError::Raise("<b>JpGraph Error:</b> Illegal argument to SetLineStyle $s");
3575        $this->line_style=$s;
3576    }
3577       
3578    // Same as Line but take the line_style into account
3579    function StyleLine($x1,$y1,$x2,$y2) {
3580        switch( $this->line_style ) {
3581            case 1:// Solid
3582                $this->Line($x1,$y1,$x2,$y2);
3583            break;
3584            case 2: // Dotted
3585                $this->DashedLine($x1,$y1,$x2,$y2,1,6);
3586            break;
3587            case 3: // Dashed
3588                $this->DashedLine($x1,$y1,$x2,$y2,2,4);
3589            break;
3590            case 4: // Longdashes
3591                $this->DashedLine($x1,$y1,$x2,$y2,8,6);
3592            break;
3593            default:
3594                JpGraphError::Raise("<b>JpGraph Error:</b> Unknown line style: $this->line_style ");
3595                break;
3596        }
3597    }
3598
3599    function Line($x1,$y1,$x2,$y2) {
3600        if( $this->line_weight==0 ) return;
3601        if( $this->use_anti_aliasing ) {
3602            $dx = $x2-$x1;
3603            $dy = $y2-$y1;
3604            // Vertical, Horizontal or 45 lines don't need anti-aliasing
3605            if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
3606                $this->WuLine($x1,$y1,$x2,$y2);
3607                return;
3608            }
3609        }
3610        if( $this->line_weight==1 )
3611            imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
3612        elseif( $x1==$x2 ) {            // Special case for vertical lines
3613            imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
3614            $w1=floor($this->line_weight/2);
3615            $w2=floor(($this->line_weight-1)/2);
3616            for($i=1; $i<=$w1; ++$i)
3617                imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
3618            for($i=1; $i<=$w2; ++$i)
3619                imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
3620        }
3621        elseif( $y1==$y2 ) {            // Special case for horizontal lines
3622            imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
3623            $w1=floor($this->line_weight/2);
3624            $w2=floor(($this->line_weight-1)/2);
3625            for($i=1; $i<=$w1; ++$i)
3626                imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
3627            for($i=1; $i<=$w2; ++$i)
3628                imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);               
3629        }
3630        else {  // General case with a line at an angle
3631            $a = atan2($y1-$y2,$x2-$x1);
3632            // Now establish some offsets from the center. This gets a little
3633            // bit involved since we are dealing with integer functions and we
3634            // want the apperance to be as smooth as possible and never be thicker
3635            // then the specified width.
3636                       
3637            // We do the trig stuff to make sure that the endpoints of the line
3638            // are perpendicular to the line itself.
3639            $dx=(sin($a)*$this->line_weight/2);
3640            $dy=(cos($a)*$this->line_weight/2);
3641
3642            $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
3643            imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
3644        }               
3645        $this->lastx=$x2; $this->lasty=$y2;             
3646    }
3647       
3648    function Polygon($p) {
3649        if( $this->line_weight==0 ) return;
3650        $n=count($p)/2;
3651        for( $i=0; $i<$n; ++$i ) {
3652            $j=($i+1)%$n;
3653            $this->Line($p[$i*2],$p[$i*2+1],$p[$j*2],$p[$j*2+1]);
3654        }
3655    }
3656       
3657    function FilledPolygon($pts) {
3658        imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
3659    }
3660       
3661    function Rectangle($xl,$yu,$xr,$yl) {
3662        $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
3663    }
3664       
3665    function FilledRectangle($xl,$yu,$xr,$yl) {
3666        $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
3667    }
3668
3669    function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
3670        $this->PushColor($shadow_color);
3671        $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl);
3672        $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
3673        $this->PopColor();
3674        if( $fcolor==false )
3675            $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
3676        else {         
3677            $this->PushColor($fcolor);
3678            $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
3679            $this->PopColor();
3680            $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);                                                 
3681        }
3682    }
3683
3684    function StyleLineTo($x,$y) {
3685        $this->StyleLine($this->lastx,$this->lasty,$x,$y);
3686        $this->lastx=$x;
3687        $this->lasty=$y;
3688    }
3689       
3690    function LineTo($x,$y) {
3691        $this->Line($this->lastx,$this->lasty,$x,$y);
3692        $this->lastx=$x;
3693        $this->lasty=$y;
3694    }
3695       
3696    function Point($x,$y) {
3697        imagesetpixel($this->img,$x,$y,$this->current_color);
3698    }
3699       
3700    function Fill($x,$y) {
3701        imagefill($this->img,$x,$y,$this->current_color);
3702    }
3703       
3704    function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
3705        // Code based on, but not identical to, work by Ariel Garza and James Pine
3706        $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
3707        $dx = ($x2 - $x1) / $line_length;
3708        $dy = ($y2 - $y1) / $line_length;
3709        $lastx = $x1; $lasty = $y1;
3710        $xmax = max($x1,$x2);
3711        $xmin = min($x1,$x2);
3712        $ymax = max($y1,$y2);
3713        $ymin = min($y1,$y2);
3714        for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
3715            $x = ($dash_length * $dx) + $lastx;
3716            $y = ($dash_length * $dy) + $lasty;
3717                       
3718            // The last section might overshoot so we must take a computational hit
3719            // and check this.
3720            if( $x>$xmax ) $x=$xmax;
3721            if( $y>$ymax ) $y=$ymax;
3722                       
3723            if( $x<$xmin ) $x=$xmin;
3724            if( $y<$ymin ) $y=$ymin;
3725
3726            $this->Line($lastx,$lasty,$x,$y);
3727            $lastx = $x + ($dash_space * $dx);
3728            $lasty = $y + ($dash_space * $dy);
3729        }
3730    }
3731       
3732    // Generate image header
3733    function Headers() {
3734        if ($this->expired) {
3735            header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
3736            header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
3737            header("Cache-Control: no-cache, must-revalidate");
3738            header("Pragma: no-cache");
3739        }
3740        header("Content-type: image/$this->img_format");
3741    }
3742
3743    // Adjust image quality for formats that allow this
3744    function SetQuality($q) {
3745        $this->quality = $q;
3746    }
3747       
3748    // Stream image to browser or to file
3749    function Stream($aFile="") {
3750        $func="image".$this->img_format;
3751        if( $this->img_format=="jpeg" && $this->quality != null ) {
3752            $res = @$func($this->img,$aFile,$this->quality);
3753        }
3754        else {
3755            if( $aFile != "" ) {
3756                $res = @$func($this->img,$aFile);
3757            }
3758            else
3759                $res = @$func($this->img);
3760        }
3761        if( !$res )
3762            JpGraphError::Raise("<b>JpGraph Error:</b> Can't create or stream image to file: $aFile<br>
3763Check that PHP has enough permission to write a file to the current directory.");
3764    }
3765               
3766    // Clear resource tide up by image
3767    function Destroy() {
3768        imagedestroy($this->img);
3769    }
3770       
3771    // Specify image format. Note depending on your installation
3772    // of PHP not all formats may be supported.
3773    function SetImgFormat($aFormat) {           
3774        $aFormat = strtolower($aFormat);
3775        $tst = true;
3776        $supported = imagetypes();
3777        if( $aFormat=="auto" ) {
3778            if( $supported & IMG_PNG )
3779                $this->img_format="png";
3780            elseif( $supported & IMG_JPG )
3781                $this->img_format="jpeg";
3782            elseif( $supported & IMG_GIF )
3783                $this->img_format="gif";
3784            else
3785                JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP (and GD-lib) installation does not appear to support any known graphic formats.".
3786                    "You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images".
3787                    "you must get the JPEG library. Please see the PHP docs for details.");
3788                               
3789            return true;
3790        }
3791        else {
3792            if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
3793                if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
3794                    $tst=false;
3795                elseif( $aFormat=="png" && !($supported & IMG_PNG) )
3796                    $tst=false;
3797                elseif( $aFormat=="gif" && !($supported & IMG_GIF) )   
3798                    $tst=false;
3799                else {
3800                    $this->img_format=$aFormat;
3801                    return true;
3802                }
3803            }
3804            else
3805                $tst=false;
3806            if( !$tst )
3807                JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP installation does not support the chosen graphic format: $aFormat");
3808        }
3809    }   
3810} // CLASS
3811
3812//===================================================
3813// CLASS RotImage
3814// Description: Exactly as Image but draws the image at
3815// a specified angle around a specified rotation point.
3816//===================================================
3817class RotImage extends Image {
3818    var $m=array();
3819    var $a=0;
3820    var $dx=0,$dy=0,$transx=0,$transy=0;
3821       
3822    function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
3823        $this->Image($aWidth,$aHeight,$aFormat);
3824        $this->dx=$this->left_margin+$this->plotwidth/2;
3825        $this->dy=$this->top_margin+$this->plotheight/2;
3826        $this->SetAngle($a);   
3827    }
3828       
3829    function SetCenter($dx,$dy) {
3830        $old_dx = $this->dx;
3831        $old_dy = $this->dy;
3832        $this->dx=$dx;
3833        $this->dy=$dy;
3834        return array($old_dx,$old_dy);
3835    }
3836       
3837    function SetTranslation($dx,$dy) {
3838        $old = array($this->transx,$this->transy);
3839        $this->transx = $dx;
3840        $this->transy = $dy;
3841        return $old;
3842    }
3843
3844    function SetAngle($a) {
3845        $tmp = $this->a;
3846        $this->a = $a;
3847        $a *= M_PI/180;
3848        $sa=sin($a); $ca=cos($a);
3849               
3850        // Create the rotation matrix
3851        $this->m[0][0] = $ca;
3852        $this->m[0][1] = -$sa;
3853        $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
3854        $this->m[1][0] = $sa;
3855        $this->m[1][1] = $ca;
3856        $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
3857        return $tmp;
3858    }
3859
3860    function Circle($xc,$yc,$r) {
3861        list($xc,$yc) = $this->Rotate($xc,$yc);
3862        parent::Circle($xc,$yc,$r);
3863    }
3864
3865    function FilledCircle($xc,$yc,$r) {
3866        list($xc,$yc) = $this->Rotate($xc,$yc);
3867        parent::FilledCircle($xc,$yc,$r);
3868    }
3869
3870       
3871    function Arc($xc,$yc,$w,$h,$s,$e) {
3872        list($xc,$yc) = $this->Rotate($xc,$yc);
3873        parent::Arc($xc,$yc,$w,$h,$s,$e);
3874    }
3875
3876    function FilledArc($xc,$yc,$w,$h,$s,$e) {
3877        list($xc,$yc) = $this->Rotate($xc,$yc);
3878        parent::FilledArc($xc,$yc,$w,$h,$s,$e);
3879    }
3880
3881    function SetMargin($lm,$rm,$tm,$bm) {       
3882        parent::SetMargin($lm,$rm,$tm,$bm);
3883        $this->SetAngle($this->a);
3884    }
3885       
3886    function Rotate($x,$y) {
3887        $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y + $this->m[0][2] + $this->transx);
3888        $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y + $this->m[1][2] + $this->transy);
3889        return array($x1,$y1);
3890    }
3891       
3892    function ArrRotate($pnts) {
3893        for($i=0; $i < count($pnts)-1; $i+=2)
3894            list($pnts[$i],$pnts[$i+1]) = $this->Rotate($pnts[$i],$pnts[$i+1]);
3895        return $pnts;
3896    }
3897       
3898    function Line($x1,$y1,$x2,$y2) {
3899        list($x1,$y1) = $this->Rotate($x1,$y1);
3900        list($x2,$y2) = $this->Rotate($x2,$y2);
3901        parent::Line($x1,$y1,$x2,$y2);
3902    }
3903       
3904    function Rectangle($x1,$y1,$x2,$y2) {
3905        $this->Polygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
3906    }
3907       
3908    function FilledRectangle($x1,$y1,$x2,$y2) {
3909        if( $y1==$y2 || $x1==$x2 )
3910            $this->Line($x1,$y1,$x2,$y2);
3911        else
3912            $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
3913    }
3914       
3915    function Polygon($pnts) {
3916        //Polygon uses Line() so it will be rotated through that call
3917        parent::Polygon($pnts);
3918    }
3919       
3920    function FilledPolygon($pnts) {
3921        parent::FilledPolygon($this->ArrRotate($pnts));
3922    }
3923       
3924    function Point($x,$y) {
3925        list($xp,$yp) = $this->Rotate($x,$y);
3926        parent::Point($xp,$yp);
3927    }
3928       
3929    function DashedLine($x1,$y1,$x2,$y2,$length=1,$space=4) {
3930        list($x1,$y1) = $this->Rotate($x1,$y1);
3931        list($x2,$y2) = $this->Rotate($x2,$y2);
3932        parent::DashedLine($x1,$y1,$x2,$y2,$length,$space);
3933    }
3934       
3935    function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left") {
3936        list($xp,$yp) = $this->Rotate($x,$y);
3937        parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align);
3938    }
3939}
3940
3941//===================================================
3942// CLASS ImgStreamCache
3943// Description: Handle caching of graphs to files
3944//===================================================
3945class ImgStreamCache {
3946    var $cache_dir;
3947    var $img=null;
3948    var $timeout=0;     // Infinite timeout
3949    //---------------
3950    // CONSTRUCTOR
3951    function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
3952        $this->img = &$aImg;
3953        $this->cache_dir = $aCacheDir;
3954    }
3955
3956//---------------
3957// PUBLIC METHODS       
3958
3959    // Specify a timeout (in minutes) for the file. If the file is older then the
3960    // timeout value it will be overwritten with a newer version.
3961    // If timeout is set to 0 this is the same as infinite large timeout and if
3962    // timeout is set to -1 this is the same as infinite small timeout
3963    function SetTimeout($aTimeout) {
3964        $this->timeout=$aTimeout;       
3965    }
3966       
3967    // Output image to browser and also write it to the cache
3968    function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
3969        // Some debugging code to brand the image with numbe of colors
3970        // used
3971
3972        if( JPG_DEBUG ) {
3973            $c=$aImage->SetColor("black");
3974            $t=imagecolorstotal($this->img->img);
3975            imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);                                       
3976        }
3977               
3978        if( BRAND_TIMING ) {
3979            global $tim;
3980            $t=$tim->Pop()/1000.0;
3981            $c=$aImage->SetColor("black");
3982            $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
3983            imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);                       
3984        }
3985
3986        // Check if we should stroke the image to an arbitrary file
3987        if( $aStrokeFileName!="" ) {
3988            if( $aStrokeFileName == "auto" )
3989                $aStrokeFileName = GenImgName();
3990            if( file_exists($aStrokeFileName) ) {
3991                                // Delete the old file
3992                if( !@unlink($aStrokeFileName) )
3993                    JpGraphError::Raise("<b>JpGraph Error:</b> Can't delete cached image $aStrokeFileName. Permission problem?");
3994            }
3995            $aImage->Stream($aStrokeFileName);
3996            return;
3997        }
3998
3999        if( $aCacheFileName != "" && USE_CACHE) {
4000
4001            $aCacheFileName = $this->cache_dir . $aCacheFileName;
4002            if( file_exists($aCacheFileName) ) {
4003                if( !$aInline ) {
4004                    // If we are generating image off-line (just writing to the cache)
4005                    // and the file exists and is still valid (no timeout)
4006                    // then do nothing, just return.
4007                    $diff=time()-filemtime($aCacheFileName);
4008                    if( $diff < 0 )
4009                        JpGraphError::Raise("<b>JpGraph Error:</b> Cached imagefile ($aCacheFileName) has file date in the future!!");
4010                    if( $this->timeout>0 && ($diff <= $this->timeout*60) )
4011                        return;         
4012                }                       
4013                if( !@unlink($aCacheFileName) )
4014                    JpGraphError::Raise("<b>JpGraph Error:</b> Can't delete cached image $aStrokeFileName. Permission problem?");
4015                $aImage->Stream($aCacheFileName);       
4016            }
4017            else {
4018                $this->_MakeDirs(dirname($aCacheFileName));
4019                $aImage->Stream($aCacheFileName);
4020            }
4021                       
4022            $res=true;
4023            // Set group to specified
4024            if( CACHE_FILE_GROUP != "" )
4025                $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
4026            if( CACHE_FILE_MOD != "" )
4027                $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
4028            if( !$res )
4029                JpGraphError::Raise("<b>JpGraph Error:</b> Can't set permission for cached image $aStrokeFileName. Permission problem?");
4030                       
4031            $aImage->Destroy();
4032            if( $aInline ) {
4033                if ($fh = @fopen($aCacheFileName, "rb") ) {
4034                    $this->img->Headers();
4035                    fpassthru($fh);
4036                    return;
4037                }
4038                else
4039                    JpGraphError::Raise("<b>JpGraph Error:</b> Cant open file from cache [$aFile]");
4040            }
4041        }
4042        elseif( $aInline ) {
4043            $this->img->Headers();                     
4044            $aImage->Stream(); 
4045            return;
4046        }
4047    }
4048       
4049    // Check if a given image is in cache and in that case
4050    // pass it directly on to web browser. Return false if the
4051    // image file doesn't exist or exists but is to old
4052    function GetAndStream($aCacheFileName) {
4053        $aCacheFileName = $this->cache_dir.$aCacheFileName;             
4054        if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
4055            $diff=time()-filemtime($aCacheFileName);
4056            if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
4057                return false;           
4058            }
4059            else {
4060                if ($fh = @fopen($aCacheFileName, "rb")) {
4061                    $this->img->Headers();
4062                    fpassthru($fh);
4063                    fclose($fh);
4064                    return true;
4065                }
4066                else
4067                    JpGraphError::Raise("<b>JpGraph Error:</b> Can't open cached image \"$aCacheFileName\" for reading.");
4068            }
4069        }
4070        return false;
4071    }
4072       
4073    //---------------
4074    // PRIVATE METHODS 
4075    // Create all necessary directories in a path
4076    function _MakeDirs($aFile) {
4077        $dirs = array();
4078        while (! (file_exists($aFile))) {
4079            $dirs[] = $aFile;
4080            $aFile = dirname($aFile);
4081        }
4082        for ($i = sizeof($dirs)-1; $i>=0; $i--) {
4083            if(! @mkdir($dirs[$i],0777) )
4084                JpGraphError::Raise("<b>JpGraph Error:</b> Can't create directory in $aFile. Permission problems?");
4085                               
4086            // We also specify mode here after we have changed group.
4087            // This is necessary if Apache user doesn't belong the
4088            // default group and hence can't specify group permission
4089            // in the previous mkdir() call
4090            if( CACHE_FILE_GROUP != "" ) {
4091                $res=true;
4092                $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
4093                $res &= @chmod($dirs[$i],0777);
4094                if( !$res )
4095                    JpGraphError::Raise("<b>JpGraph Error:</b> Can't set permissions for $aFile. Permission problems?");
4096            }
4097        }
4098        return true;
4099    }   
4100} // CLASS Cache
4101       
4102//===================================================
4103// CLASS Legend
4104// Description: Responsible for drawing the box containing
4105// all the legend text for the graph
4106//===================================================
4107class Legend {
4108    var $color=array(0,0,0); // Default fram color
4109    var $fill_color=array(235,235,235); // Default fill color
4110    var $shadow=true; // Shadow around legend "box"
4111    var $txtcol=array();
4112    var $mark_abs_size=10,$xmargin=5,$ymargin=5,$shadow_width=2;
4113    var $xpos=0.05, $ypos=0.15, $halign="right", $valign="top";
4114    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
4115    var $hide=false,$layout=LEGEND_VERT;
4116    var $weight=1;
4117//---------------
4118// CONSTRUCTOR
4119    function Legend() {
4120        // Empty
4121    }
4122//---------------
4123// PUBLIC METHODS       
4124    function Hide($aHide=true) {
4125        $this->hide=$aHide;
4126    }
4127       
4128    function SetShadow($aShow=true,$aWidth=2) {
4129        $this->shadow=$aShow;
4130        $this->shadow_width=$aWidth;
4131    }
4132       
4133    function SetLineWeight($aWeight) {
4134        $this->weight = $aWeight;
4135    }
4136       
4137    function SetLayout($aDirection=LEGEND_VERT) {
4138        $this->layout=$aDirection;
4139    }
4140       
4141    // Set color on frame around box
4142    function SetColor($aColor) {
4143        $this->color=$aColor;
4144    }
4145       
4146    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
4147        $this->font_family = $aFamily;
4148        $this->font_style = $aStyle;
4149        $this->font_size = $aSize;
4150    }
4151       
4152    function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
4153        if( !($aX<1 && $aY<1) )
4154            JpGraphError::Raise("<b>JpGraph Error:</b> Position for legend must be given as percentage in range 0-1");
4155        $this->xpos=$aX;
4156        $this->ypos=$aY;
4157        $this->halign=$aHAlign;
4158        $this->valign=$aVAlign;
4159    }
4160
4161    function SetBackground($aDummy) {
4162        JpGraphError::Raise("<b>JpGraph Error:</b> Deprecated function Legend::SetBaqckground() use Legend::SetFillColor() instead.");
4163    }
4164
4165    function SetFillColor($aColor) {
4166        $this->fill_color=$aColor;
4167    }
4168       
4169    function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=1) {
4170        $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle);
4171    }
4172       
4173    function Stroke(&$aImg) {
4174        if( $this->hide ) return;
4175
4176        $nbrplots=count($this->txtcol);
4177        if( $nbrplots==0 ) return;
4178               
4179        $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 
4180        if( $this->layout==LEGEND_VERT )               
4181            $abs_height=$aImg->GetFontHeight() + $this->mark_abs_size*$nbrplots +
4182                $this->ymargin*($nbrplots-1);
4183        else
4184            $abs_height=2*$this->mark_abs_size+$this->ymargin;
4185                                               
4186        if( $this->shadow ) $abs_height += $this->shadow_width;
4187        $mtw=0;
4188        foreach($this->txtcol as $p) {
4189            if( $this->layout==LEGEND_VERT )
4190                $mtw=max($mtw,$aImg->GetTextWidth($p[0]));
4191            else
4192                $mtw+=$aImg->GetTextWidth($p[0])+$this->mark_abs_size+$this->xmargin;
4193        }
4194        $abs_width=$mtw+2*$this->mark_abs_size+2*$this->xmargin;
4195        if( $this->halign=="left" )
4196            $xp=$this->xpos*$aImg->width;
4197        elseif( $this->halign=="center" )
4198            $xp=$this->xpos*$aImg->width - $abs_width/2;
4199        else 
4200            $xp = $aImg->width - $this->xpos*$aImg->width - $abs_width;
4201        $yp=$this->ypos*$aImg->height;
4202        if( $this->valign=="center" )
4203            $yp-=$abs_height/2;
4204        elseif( $this->valign=="bottom" )
4205            $yp-=$abs_height;
4206                       
4207        $aImg->SetColor($this->color);                         
4208        $aImg->SetLineWeight($this->weight);
4209        if( $this->shadow )
4210            $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height,$this->fill_color,$this->shadow_width);
4211        else {
4212            $aImg->SetColor($this->fill_color);                         
4213            $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
4214            $aImg->SetColor($this->color);                                                     
4215            $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
4216        }
4217        $aImg->SetLineWeight(1);
4218        $x1=$xp+$this->mark_abs_size/2;
4219        $y1=$yp+$aImg->GetFontHeight()*0.5;
4220        foreach($this->txtcol as $p) {
4221            $aImg->SetColor($p[1]);
4222            if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
4223                $p[2]->Stroke($aImg,$x1+$this->mark_abs_size/2,$y1+$aImg->GetFontHeight()/2);
4224            }
4225            elseif ( $p[2] != "" ) {
4226                $aImg->SetLineStyle($p[3]);
4227                $aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2);
4228                $aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2-1,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2-1);
4229            }
4230            else {
4231                $aImg->FilledRectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
4232                $aImg->SetColor($this->color);
4233                $aImg->Rectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
4234            }
4235            $aImg->SetColor($this->color);
4236            $aImg->SetTextAlign("left");                       
4237            $aImg->StrokeText($x1+$this->mark_abs_size+$this->xmargin,$y1+$this->mark_abs_size,$p[0]);
4238            if( $this->layout==LEGEND_VERT )
4239                $y1 += $this->ymargin+$this->mark_abs_size;
4240            else
4241                $x1 += 2*$this->ymargin+$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
4242        }                                                                                                                                                       
4243    }
4244} // Class
4245       
4246//===================================================
4247// CLASS Plot
4248// Description: Abstract base class for all concrete plot classes
4249//===================================================
4250class Plot {
4251    var $line_weight=1;
4252    var $coords=array();
4253    var $legend="";
4254    var $csimtargets=array();   // Array of targets for CSIM
4255    var $csimareas="";                  // Resultant CSIM area tags     
4256    var $csimalts=null;                 // ALT:s for corresponding target
4257    var $color="black";
4258    var $numpoints=0;
4259    var $weight=1;     
4260//---------------
4261// CONSTRUCTOR
4262    function Plot(&$aDatay,$aDatax=false) {
4263        $this->numpoints = count($aDatay);
4264        if( $this->numpoints==0 )
4265            JpGraphError::Raise("<b>JpGraph Error:</b> Empty data array specified for plot. Must have at least one data point.");
4266        $this->coords[0]=$aDatay;
4267        if( is_array($aDatax) )
4268            $this->coords[1]=$aDatax;
4269    }
4270
4271//---------------
4272// PUBLIC METHODS       
4273
4274    // Stroke the plot
4275    // "virtual" function which must be implemented by
4276    // the subclasses
4277    function Stroke(&$aImg,&$aXScale,&$aYScale) {
4278        JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
4279    }
4280       
4281    // Set href targets for CSIM       
4282    function SetCSIMTargets(&$aTargets,$aAlts=null) {
4283        $this->csimtargets=$aTargets;
4284        $this->csimalts=$aAlts;         
4285    }
4286       
4287    // Get all created areas
4288    function GetCSIMareas() {
4289        return $this->csimareas;
4290    }   
4291       
4292    // "Virtual" function which gets called before any scale
4293    // or axis are stroked used to do any plot specific adjustment
4294    function PreStrokeAdjust(&$aGraph) {
4295        if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
4296            JpGraphError::Raise("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
4297        return true;   
4298    }
4299       
4300    function SetWeight($aWeight) {
4301        $this->weight=$aWeight;
4302    }
4303       
4304    // Get minimum values in plot
4305    function Min() {
4306        if( isset($this->coords[1]) )
4307            $x=$this->coords[1];
4308        else
4309            $x="";
4310        if( $x != "" && count($x) > 0 )
4311            $xm=min($x);
4312        else
4313            $xm=0;
4314        $y=$this->coords[0];
4315        if( count($y) > 0 ) {
4316            $ym = $y[0];
4317            $cnt = count($y);
4318            $i=0;
4319            while( $i<$cnt && !is_numeric($ym=$y[$i]) )
4320                $i++;
4321            while( $i < $cnt) {
4322                if( is_numeric($y[$i]) )
4323                    $ym=min($ym,$y[$i]);
4324                ++$i;
4325            }                   
4326        }
4327        else
4328            $ym="";
4329        return array($xm,$ym);
4330    }
4331       
4332    // Get maximum value in plot
4333    function Max() {
4334        if( isset($this->coords[1]) )
4335            $x=$this->coords[1];
4336        else
4337            $x="";
4338
4339        if( $x!="" && count($x) > 0 )
4340            $xm=max($x);
4341        else
4342            $xm=count($this->coords[0])-1;      // We count from 0..(n-1)
4343        $y=$this->coords[0];
4344        if( count($y) > 0 ) {
4345            if( !isset($y[0]) ) {
4346                $y[0] = 0;
4347// Change in 1.5.1 Don't treat this as an error any more. Just silently concert to 0
4348//              JpGraphError::Raise("<b>JpGraph Error:</b> You have not specified a y[0] value!!");
4349            }
4350            $cnt = count($y);
4351            $i=0;
4352            while( $i<$cnt && !is_numeric($ym=$y[$i]) )
4353                $i++;                           
4354            while( $i < $cnt ) {
4355                if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
4356                ++$i;
4357            }
4358        }
4359        else
4360            $ym="";
4361        return array($xm,$ym);
4362    }
4363       
4364    function SetColor($aColor) {
4365        $this->color=$aColor;
4366    }
4367       
4368    function SetLegend($aLegend) {
4369        $this->legend = $aLegend;
4370    }
4371       
4372    function SetLineWeight($aWeight=1) {
4373        $this->line_weight=$aWeight;
4374    }
4375       
4376    // This method gets called by Graph class to plot anything that should go
4377    // into the margin after the margin color has been set.
4378    function StrokeMargin(&$aImg) {
4379        return true;
4380    }
4381
4382    // Framework function the chance for each plot class to set a legend
4383    function Legend(&$aGraph) {
4384        if( $this->legend!="" )
4385            $aGraph->legend->Add($this->legend,$this->color);           
4386    }
4387       
4388} // Class
4389
4390
4391//===================================================
4392// CLASS PlotMark
4393// Description: Handles the plot marks in graphs
4394// mostly used in line and scatter plots.
4395//===================================================
4396class PlotMark {
4397    var $title, $show=true;
4398    var $type=-1, $weight=1;
4399    var $color="black", $width=5, $fill_color="blue";
4400//      --------------
4401// CONSTRUCTOR
4402    function PlotMark() {
4403        $this->title = new Text();
4404        $this->title->Hide();
4405    }
4406//---------------
4407// PUBLIC METHODS       
4408    function SetType($t) {
4409        $this->type = $t;
4410    }
4411       
4412    function GetType() {
4413        return $this->type;
4414    }
4415       
4416    function SetColor($c) {
4417        $this->color=$c;
4418    }
4419       
4420    function SetFillColor($c) {
4421        $this->fill_color = $c;
4422    }
4423       
4424    function SetWidth($w) {
4425        $this->width=$w;
4426    }
4427       
4428    function GetWidth() {
4429        return $this->width;
4430    }
4431       
4432    function Hide($aHide=true) {
4433        $this->show = !$aHide;
4434    }
4435       
4436    function Show($aShow=true) {
4437        $this->show = $aShow;
4438    }
4439       
4440    function Stroke(&$img,$x,$y) {
4441        if( !$this->show ) return;
4442        $dx=round($this->width/2,0);
4443        $dy=round($this->width/2,0);
4444        $pts=0;         
4445        switch( $this->type ) {
4446            case MARK_SQUARE:
4447                $c[]=$x-$dx;$c[]=$y-$dy;
4448                $c[]=$x+$dx;$c[]=$y-$dy;
4449                $c[]=$x+$dx;$c[]=$y+$dy;
4450                $c[]=$x-$dx;$c[]=$y+$dy;
4451                $pts=4;
4452                break;
4453            case MARK_UTRIANGLE:
4454                ++$dx;++$dy;
4455                $c[]=$x-$dx;$c[]=$y+0.87*$dy;   // tan(60)/2*$dx
4456                $c[]=$x;$c[]=$y-0.87*$dy;
4457                $c[]=$x+$dx;$c[]=$y+0.87*$dy;
4458                $pts=3;
4459                break;
4460            case MARK_DTRIANGLE:
4461                ++$dx;++$dy;                   
4462                $c[]=$x;$c[]=$y+0.87*$dy;       // tan(60)/2*$dx
4463                $c[]=$x-$dx;$c[]=$y-0.87*$dy;
4464                $c[]=$x+$dx;$c[]=$y-0.87*$dy;
4465                $pts=3;
4466                break;                         
4467            case MARK_DIAMOND:
4468                $c[]=$x;$c[]=$y+$dy;
4469                $c[]=$x-$dx;$c[]=$y;
4470                $c[]=$x;$c[]=$y-$dy;
4471                $c[]=$x+$dx;$c[]=$y;
4472                $pts=4;
4473                break;                         
4474        }
4475        if( $pts>0 ) {
4476            $img->SetLineWeight($this->weight);
4477            $img->SetColor($this->fill_color);                                                         
4478            $img->FilledPolygon($c);
4479            $img->SetColor($this->color);                                       
4480            $img->Polygon($c);
4481        }
4482        elseif( $this->type==MARK_CIRCLE ) {
4483            $img->SetColor($this->color);                                       
4484            $img->Circle($x,$y,$this->width);
4485        }
4486        elseif( $this->type==MARK_FILLEDCIRCLE ) {
4487            $img->SetColor($this->fill_color);         
4488            $img->FilledCircle($x,$y,$this->width);
4489            $img->SetColor($this->color);               
4490            $img->Circle($x,$y,$this->width);
4491        }
4492        elseif( $this->type==MARK_CROSS ) {
4493            // Oversize by a pixel to match the X
4494            $img->SetColor($this->color);
4495            $img->SetLineWeight($this->weight);
4496            $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
4497            $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
4498        }
4499        elseif( $this->type==MARK_X ) {
4500            $img->SetColor($this->color);
4501            $img->SetLineWeight($this->weight);
4502            $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
4503            $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);           
4504        }                       
4505        elseif( $this->type==MARK_STAR ) {
4506            $img->SetColor($this->color);
4507            $img->SetLineWeight($this->weight);
4508            $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
4509            $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);
4510            // Oversize by a pixel to match the X                               
4511            $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
4512            $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
4513        }
4514               
4515        // Stroke title
4516        $this->title->Align("center","center");
4517        $this->title->Stroke($img,$x,$y);                       
4518    }
4519} // Class
4520
4521//==============================================================================
4522// The following section contains classes to implement the "band" functionality
4523//==============================================================================
4524
4525// Utility class to hold coordinates for a rectangle
4526class Rectangle {
4527    var $x,$y,$w,$h;
4528    var $xe, $ye;
4529    function Rectangle($aX,$aY,$aWidth,$aHeight) {
4530        $this->x=$aX;
4531        $this->y=$aY;
4532        $this->w=$aWidth;
4533        $this->h=$aHeight;
4534        $this->xe=$aX+$aWidth-1;
4535        $this->ye=$aY+$aHeight-1;
4536    }
4537}
4538
4539//=====================================================================
4540// Class RectPattern
4541// Base class for pattern hierarchi that is used to display patterned
4542// bands on the graph. Any subclass that doesn't override Stroke()
4543// must at least implement method DoPattern(&$aImg) which is responsible
4544// for drawing the pattern onto the graph.
4545//=====================================================================
4546class RectPattern {
4547    var $color;
4548    var $weight;
4549    var $rect=null;
4550    var $doframe=true;
4551    var $linespacing;   // Line spacing in pixels
4552    var $iBackgroundColor=-1;  // Default is no background fill
4553       
4554    function RectPattern($aColor,$aWeight=1) {
4555        $this->color = $aColor;
4556        $this->weight = $aWeight;               
4557    }
4558
4559    function SetBackground($aBackgroundColor) {
4560        $this->iBackgroundColor=$aBackgroundColor;
4561    }
4562
4563    function SetPos(&$aRect) {
4564        $this->rect = $aRect;
4565    }
4566       
4567    function ShowFrame($aShow=true) {
4568        $this->doframe=$aShow;
4569    }
4570
4571    function SetDensity($aDens) {
4572        if( $aDens <1 || $aDens > 100 )
4573            JpGraphError::Raise("<b>JpGraph Error:</b> Desity for pattern must be between 1 and 100. (You tried $aDens)");
4574        // 1% corresponds to linespacing=50
4575        // 100 % corresponds to linespacing 1
4576        $this->linespacing = floor(((100-$aDens)/100.0)*50)+1;
4577
4578    }
4579
4580    function Stroke(&$aImg) {
4581        if( $this->rect == null )
4582            JpGraphError::Raise("<b>JpGraph Error:</b> No positions specified for pattern.");
4583
4584        if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
4585            $aImg->SetColor($this->iBackgroundColor);
4586            $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
4587        }
4588
4589        $aImg->SetColor($this->color);
4590        $aImg->SetLineWeight($this->weight);
4591
4592        // Virtual function implemented by subclass
4593        $this->DoPattern($aImg);
4594
4595        // Frame around the pattern area
4596        if( $this->doframe )
4597            $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
4598    }
4599
4600}
4601
4602
4603//=====================================================================
4604// Class RectPatternSolid
4605// Implements a solid band
4606//=====================================================================
4607class RectPatternSolid extends RectPattern {
4608
4609    function RectPatternSolid($aColor="black",$aWeight=1) {
4610        parent::RectPattern($aColor,$aWeight);
4611    }
4612
4613    function Stroke(&$aImg) {
4614        $aImg->SetColor($this->color);
4615        $aImg->FilledRectangle($this->rect->x,$this->rect->y,
4616        $this->rect->xe,$this->rect->ye);
4617    }
4618}
4619
4620//=====================================================================
4621// Class RectPatternHor
4622// Implements horizontal line pattern
4623//=====================================================================
4624class RectPatternHor extends RectPattern {
4625               
4626    function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
4627        parent::RectPattern($aColor,$aWeight);
4628        $this->linespacing = $aLineSpacing;
4629    }
4630               
4631    function DoPattern(&$aImg) {
4632        $x0 = $this->rect->x;           
4633        $x1 = $this->rect->xe;
4634        $y = $this->rect->y;
4635        while( $y < $this->rect->ye ) {
4636            $aImg->Line($x0,$y,$x1,$y);
4637            $y += $this->linespacing;
4638        }
4639    }
4640}
4641
4642//=====================================================================
4643// Class RectPatternVert
4644// Implements vertical line pattern
4645//=====================================================================
4646class RectPatternVert extends RectPattern {
4647    var $linespacing=10;        // Line spacing in pixels
4648               
4649    function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
4650        parent::RectPattern($aColor,$aWeight);
4651        $this->linespacing = $aLineSpacing;
4652    }
4653
4654    //--------------------
4655    // Private methods
4656    //
4657    function DoPattern(&$aImg) {
4658        $x = $this->rect->x;           
4659        $y0 = $this->rect->y;
4660        $y1 = $this->rect->ye;
4661        while( $x < $this->rect->xe ) {
4662            $aImg->Line($x,$y0,$x,$y1);
4663            $x += $this->linespacing;
4664        }
4665    }
4666}
4667
4668
4669//=====================================================================
4670// Class RectPatternRDiag
4671// Implements right diagonal pattern
4672//=====================================================================
4673class RectPatternRDiag extends RectPattern {
4674    var $linespacing;   // Line spacing in pixels
4675               
4676    function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
4677        parent::RectPattern($aColor,$aWeight);
4678        $this->linespacing = $aLineSpacing;
4679    }
4680
4681    function DoPattern(&$aImg) {
4682        //  --------------------
4683        //  | /   /   /   /   /|
4684        //  |/   /   /   /   / |
4685        //  |   /   /   /   /  |
4686        //  --------------------
4687        $xe = $this->rect->xe;
4688        $ye = $this->rect->ye;
4689        $x0 = $this->rect->x + round($this->linespacing/2);
4690        $y0 = $this->rect->y;
4691        $x1 = $this->rect->x;
4692        $y1 = $this->rect->y + round($this->linespacing/2);
4693
4694        while($x0<=$xe && $y1<=$ye) {
4695            $aImg->Line($x0,$y0,$x1,$y1);
4696            $x0 += $this->linespacing;
4697            $y1 += $this->linespacing;
4698        }
4699
4700        $x1 = $this->rect->x + ($y1-$ye);
4701        //$x1 = $this->rect->x +$this->linespacing;
4702        $y0=$this->rect->y; $y1=$ye;
4703        while( $x0 <= $xe ) {
4704            $aImg->Line($x0,$y0,$x1,$y1);
4705            $x0 += $this->linespacing;
4706            $x1 += $this->linespacing;
4707        }
4708
4709        $y0=$this->rect->y + ($x0-$xe);
4710        $x0=$xe;
4711        while( $y0 <= $ye ) {
4712            $aImg->Line($x0,$y0,$x1,$y1);
4713            $y0 += $this->linespacing;
4714            $x1 += $this->linespacing;
4715        }
4716    }
4717
4718}
4719
4720//=====================================================================
4721// Class RectPatternLDiag
4722// Implements left diagonal pattern
4723//=====================================================================
4724class RectPatternLDiag extends RectPattern {
4725    var $linespacing;   // Line spacing in pixels
4726               
4727    function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
4728        $this->linespacing = $aLineSpacing;
4729        parent::RectPattern($aColor,$aWeight);
4730    }
4731
4732    function DoPattern(&$aImg) {
4733        //  --------------------
4734        //  |\   \   \   \   \ |
4735        //  | \   \   \   \   \|
4736        //  |  \   \   \   \   |
4737        //  |------------------|
4738        $xe = $this->rect->xe;
4739        $ye = $this->rect->ye;
4740        $x0 = $this->rect->x + round($this->linespacing/2);
4741        $y0 = $this->rect->ye;
4742        $x1 = $this->rect->x;
4743        $y1 = $this->rect->ye - round($this->linespacing/2);
4744
4745        while($x0<=$xe && $y1>=$this->rect->y) {
4746            $aImg->Line($x0,$y0,$x1,$y1);
4747            $x0 += $this->linespacing;
4748            $y1 -= $this->linespacing;
4749        }
4750
4751        $x1 = $this->rect->x + ($this->rect->y-$y1);
4752        $y0=$ye; $y1=$this->rect->y;
4753        while( $x0 <= $xe ) {
4754            $aImg->Line($x0,$y0,$x1,$y1);
4755            $x0 += $this->linespacing;
4756            $x1 += $this->linespacing;
4757        }
4758
4759        $y0=$this->rect->ye - ($x0-$xe);
4760        $x0=$xe;
4761        while( $y0 >= $this->rect->y ) {
4762            $aImg->Line($x0,$y0,$x1,$y1);
4763            $y0 -= $this->linespacing;
4764            $x1 += $this->linespacing;
4765        }
4766    }
4767}
4768
4769//=====================================================================
4770// Class RectPattern3DPlane
4771// Implements "3D" plane pattern
4772//=====================================================================
4773class RectPattern3DPlane extends RectPattern {
4774    var $alpha=50;  // Parameter that specifies the distance
4775    // to "simulated" horizon in pixel from the
4776    // top of the band. Specifies how fast the lines
4777    // converge.
4778
4779    function RectPattern3DPlane($aColor="black",$aWeight=1) {
4780        parent::RectPattern($aColor,$aWeight);
4781        $this->SetDensity(10);  // Slightly larger default
4782    }
4783
4784    function SetHorizon($aHorizon) {
4785        $this->alpha=$aHorizon;
4786    }
4787       
4788    function DoPattern(&$aImg) {
4789        // "Fake" a nice 3D grid-effect.
4790        $x0 = $this->rect->x + $this->rect->w/2;
4791        $y0 = $this->rect->y;
4792        $x1 = $x0;
4793        $y1 = $this->rect->ye;
4794        $x0_right = $x0;
4795        $x1_right = $x1;
4796
4797        // BTW "apa" means monkey in Swedish but is really a shortform for
4798        // "alpha+a" which was the labels I used on paper when I derived the
4799        // geometric to get the 3D perspective right.
4800        // $apa is the height of the bounding rectangle plus the distance to the
4801        // artifical horizon (alpha)
4802        $apa = $this->rect->h + $this->alpha;
4803
4804        // Three cases and three loops
4805        // 1) The endpoint of the line ends on the bottom line
4806        // 2) The endpoint ends on the side
4807        // 3) Horizontal lines
4808
4809        // Endpoint falls on bottom line
4810        $middle=$this->rect->x + $this->rect->w/2;
4811        $dist=$this->linespacing;
4812        $factor=$this->alpha /($apa);
4813        while($x1>$this->rect->x) {
4814            $aImg->Line($x0,$y0,$x1,$y1);
4815            $aImg->Line($x0_right,$y0,$x1_right,$y1);
4816            $x1 = $middle - $dist;
4817            $x0 = $middle - $dist * $factor;
4818            $x1_right = $middle + $dist;
4819            $x0_right =  $middle + $dist * $factor;
4820            $dist += $this->linespacing;
4821        }
4822
4823        // Endpoint falls on sides
4824        $dist -= $this->linespacing;
4825        $d=$this->rect->w/2;
4826        $c = $apa - $d*$apa/$dist;
4827        while( $x0>$this->rect->x ) {
4828            $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
4829            $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
4830            $dist += $this->linespacing;                       
4831            $x0 = $middle - $dist * $factor;
4832            $x1 = $middle - $dist;
4833            $x0_right =  $middle + $dist * $factor;                     
4834            $c = $apa - $d*$apa/$dist;
4835        }               
4836               
4837        // Horizontal lines
4838        // They need some serious consideration since they are a function
4839        // of perspective depth (alpha) and density (linespacing)
4840        $x0=$this->rect->x;
4841        $x1=$this->rect->xe;
4842        $y=$this->rect->ye;
4843               
4844        // The first line is drawn directly. Makes the loop below slightly
4845        // more readable.
4846        $aImg->Line($x0,$y,$x1,$y);
4847        $hls = $this->linespacing;
4848               
4849        // A correction factor for vertical "brick" line spacing to account for
4850        // a) the difference in number of pixels hor vs vert
4851        // b) visual apperance to make the first layer of "bricks" look more
4852        // square.
4853        $vls = $this->linespacing*0.6;
4854               
4855        $ds = $hls*($apa-$vls)/$apa;
4856        // Get the slope for the "perspective line" going from bottom right
4857        // corner to top left corner of the "first" brick.
4858               
4859        // Uncomment the following lines if you want to get a visual understanding
4860        // of what this helpline does. BTW this mimics the way you would get the
4861        // perspective right when drawing on paper.
4862        /*
4863          $x0 = $middle;
4864          $y0 = $this->rect->ye;
4865          $len=floor(($this->rect->ye-$this->rect->y)/$vls);
4866          $x1 = $middle-round($len*$ds);
4867          $y1 = $this->rect->ye-$len*$vls;
4868          $aImg->PushColor("red");
4869          $aImg->Line($x0,$y0,$x1,$y1);
4870          $aImg->PopColor();
4871        */
4872               
4873        $y -= $vls;             
4874        $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
4875        $dist = $hls;
4876        while( $y>$this->rect->y ) {
4877            $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
4878            $adj = $k*$dist/(1+$dist*$k/$apa);
4879            if( $adj < 2 ) $adj=2;
4880            $y = $this->rect->ye - round($adj);
4881            $dist += $hls;
4882        }
4883    }
4884}
4885
4886//=====================================================================
4887// Class RectPatternCross
4888// Vert/Hor crosses
4889//=====================================================================
4890class RectPatternCross extends RectPattern {
4891    var $vert=null;
4892    var $hor=null;
4893    function RectPatternCross($aColor="black",$aWeight=1) {
4894        parent::RectPattern($aColor,$aWeight);
4895        $this->vert = new RectPatternVert($aColor,$aWeight);
4896        $this->hor  = new RectPatternHor($aColor,$aWeight);
4897    }
4898
4899    function SetOrder($aDepth) {
4900        $this->vert->SetOrder($aDepth);
4901        $this->hor->SetOrder($aDepth);
4902    }
4903
4904    function SetPos(&$aRect) {
4905        parent::SetPos($aRect);
4906        $this->vert->SetPos($aRect);
4907        $this->hor->SetPos($aRect);
4908    }
4909
4910    function SetDensity($aDens) {
4911        $this->vert->SetDensity($aDens);
4912        $this->hor->SetDensity($aDens);
4913    }
4914
4915    function DoPattern(&$aImg) {
4916        $this->vert->DoPattern($aImg);
4917        $this->hor->DoPattern($aImg);
4918    }
4919}
4920
4921//=====================================================================
4922// Class RectPatternDiagCross
4923// Vert/Hor crosses
4924//=====================================================================
4925
4926class RectPatternDiagCross extends RectPattern {
4927    var $left=null;
4928    var $right=null;
4929    function RectPatternDiagCross($aColor="black",$aWeight=1) {
4930        parent::RectPattern($aColor,$aWeight);
4931        $this->right = new RectPatternRDiag($aColor,$aWeight);
4932        $this->left  = new RectPatternLDiag($aColor,$aWeight);
4933    }
4934
4935    function SetOrder($aDepth) {
4936        $this->left->SetOrder($aDepth);
4937        $this->right->SetOrder($aDepth);
4938    }
4939
4940    function SetPos(&$aRect) {
4941        parent::SetPos($aRect);
4942        $this->left->SetPos($aRect);
4943        $this->right->SetPos($aRect);
4944    }
4945
4946    function SetDensity($aDens) {
4947        $this->left->SetDensity($aDens);
4948        $this->right->SetDensity($aDens);
4949    }
4950
4951    function DoPattern(&$aImg) {
4952        $this->left->DoPattern($aImg);
4953        $this->right->DoPattern($aImg);
4954    }
4955
4956}
4957
4958//=====================================================================
4959// Class RectPatternFactory
4960// Factory class for rectangular pattern
4961//=====================================================================
4962class RectPatternFactory {
4963    function RectPatternFactory() {
4964        // Empty
4965    }
4966    function Create($aPattern,$aColor,$aWeight=1) {
4967        switch($aPattern) {
4968            case BAND_RDIAG:
4969                $obj =  new RectPatternRDiag($aColor,$aWeight);
4970            break;
4971            case BAND_LDIAG:
4972                $obj =  new RectPatternLDiag($aColor,$aWeight);
4973            break;
4974            case BAND_SOLID:
4975                $obj =  new RectPatternSolid($aColor,$aWeight);
4976            break;
4977            case BAND_LVERT:
4978                $obj =  new RectPatternVert($aColor,$aWeight);
4979            break;
4980            case BAND_LHOR:
4981                $obj =  new RectPatternHor($aColor,$aWeight);
4982            break;
4983            case BAND_3DPLANE:
4984                $obj =  new RectPattern3DPlane($aColor,$aWeight);
4985            break;
4986            case BAND_HVCROSS:
4987                $obj =  new RectPatternCross($aColor,$aWeight);
4988            break;
4989            case BAND_DIAGCROSS:
4990                $obj =  new RectPatternDiagCross($aColor,$aWeight);
4991            break;
4992            default:
4993                JpGraphError::Raise("<b>JpGraph Error:</b> Unknown pattern specification ($aPattern)");
4994        }
4995        return $obj;
4996    }
4997}
4998
4999
5000//=====================================================================
5001// Class PlotBand
5002// Factory class which is used by the client.
5003// It is reposnsible for factoring the corresponding pattern
5004// concrete class.
5005//=====================================================================
5006class PlotBand {
5007    var $prect=null;
5008    var $depth;
5009
5010    function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
5011        $f =  new RectPatternFactory();
5012        $this->prect = $f->Create($aPattern,$aColor,$aWeight);
5013        $this->dir = $aDir;
5014        $this->min = $aMin;
5015        $this->max = $aMax;
5016        $this->depth=$aDepth;
5017    }
5018       
5019    // Set position. aRect contains absolute image coordinates
5020    function SetPos(&$aRect) {
5021        assert( $this->prect != null ) ;
5022        $this->prect->SetPos($aRect);
5023    }
5024       
5025    function ShowFrame($aFlag=true) {
5026        $this->prect->ShowFrame($aFlag);
5027    }
5028
5029    // Set z-order. In front of pplot or in the back
5030    function SetOrder($aDepth) {
5031        $this->depth=$aDepth;
5032    }
5033       
5034    function SetDensity($aDens) {
5035        $this->prect->SetDensity($aDens);
5036    }
5037       
5038    function GetDir() {
5039        return $this->dir;
5040    }
5041       
5042    function GetMin() {
5043        return $this->min;
5044    }
5045       
5046    function GetMax() {
5047        return $this->max;
5048    }
5049       
5050    // Display band
5051    function Stroke(&$aImg,&$aXScale,&$aYScale) {
5052        assert( $this->prect != null ) ;
5053        if( $this->dir == HORIZONTAL ) {
5054            if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aYScale->GetMinVal();
5055            if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aYScale->GetMaxVal();
5056            $x=$aXScale->scale_abs[0];
5057            $y=$aYScale->Translate($this->max);
5058            $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
5059            $height=abs($y-$aYScale->Translate($this->min))+1;
5060            $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
5061        }
5062        else {  // VERTICAL
5063            if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aXScale->GetMinVal();
5064            if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aXScale->GetMaxVal();
5065            $y=$aYScale->scale_abs[1];
5066            $x=$aXScale->Translate($this->min);
5067            $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
5068            $width=abs($x-$aXScale->Translate($this->max));
5069            $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
5070        }
5071        $this->prect->Stroke($aImg);
5072    }
5073}
5074
5075//===================================================
5076// CLASS PlotLine
5077// Description:
5078// Data container class to hold properties for a static
5079// line that is drawn directly in the plot area.
5080// Usefull to add static borders inside a plot to show
5081// for example set-values
5082//===================================================
5083class PlotLine {
5084    var $weight=1;
5085    var $color="black";
5086    var $direction=-1;
5087    var $scaleposition;
5088
5089//---------------
5090// CONSTRUCTOR
5091    function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
5092        $this->direction = $aDir;
5093        $this->color=$aColor;
5094        $this->weight=$aWeight;
5095        $this->scaleposition=$aPos;
5096    }
5097       
5098//---------------
5099// PUBLIC METHODS       
5100    function SetPosition($aScalePosition) {
5101        $this->scaleposition=$aScalePosition;
5102    }
5103       
5104    function SetDirection($aDir) {
5105        $this->direction = $aDir;
5106    }
5107       
5108    function SetColor($aColor) {
5109        $this->color=$aColor;
5110    }
5111       
5112    function SetWeight($aWeight) {
5113        $this->weight=$aWeight;
5114    }
5115       
5116    function Stroke(&$aImg,&$aXScale,&$aYScale) {
5117        $aImg->SetColor($this->color);
5118        $aImg->SetLineWeight($this->weight);           
5119        if( $this->direction == VERTICAL ) {
5120            $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
5121            $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
5122            $xpos_abs=$aXScale->Translate($this->scaleposition);
5123            $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
5124        }
5125        elseif( $this->direction == HORIZONTAL ) {
5126            $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
5127            $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
5128            $ypos_abs=$aYScale->Translate($this->scaleposition);
5129            $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
5130        }
5131        else
5132            JpGraphError::Raise("<b>JpGraph Error:</b> Illegal direction for static line");
5133    }
5134}
5135
5136// <EOF>
5137?>
Note: See TracBrowser for help on using the repository browser.