Source for file jpgraph.php

Documentation is available at jpgraph.php

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

Documentation generated on Sun, 13 Mar 2005 14:25:10 +0100 by phpDocumentor 1.3.0RC3