1<?php 2 3/* $Id: phplot.php,v 1.116 2006/11/13 23:17:26 lbayuk Exp $ */ 4 5/* 6 * PHPLOT Version 5.0rc3 7 * Copyright (C) 1998-2006 Afan Ottenheimer. Released under 8 * the GPL and PHP licenses as stated in the the README file which should 9 * have been included with this document. 10 * 11 * Recent (2003-2004) work by Miguel de Benito Delgado <nonick AT vodafone DOT es> 12 * 13 * Requires PHP 4.3.0 or later 14 */ 15 16if (! defined(__FUNCTION__)) 17 define(__FUNCTION__, '__FUNCTION__ Requires at least PHP 4.3.0.'); 18 19define ('MINY', -1); // Indexes in $data (for DrawXDataLine()) 20define ('MAXY', -2); 21define ('TOTY', -3); 22 23error_reporting(E_ALL); 24 25class PHPlot { 26 27 /* I have removed internal variable declarations, some isset() checking was required, 28 * but now the variables left are those which can be tweaked by the user. This is intended to 29 * be the first step towards moving most of the Set...() methods into a subclass which will be 30 * used only when strictly necessary. Many users will be able to put default values here in the 31 * class and thus avoid memory overhead and reduce parsing times. 32 */ 33 //////////////// CONFIG PARAMETERS ////////////////////// 34 35 var $is_inline = FALSE; // FALSE = Sends headers, TRUE = sends just raw image data 36 var $browser_cache = FALSE; // FALSE = Sends headers for browser to not cache the image, 37 // (only if is_inline = FALSE also) 38 39 var $safe_margin = 5; // Extra margin used in several places. In pixels 40 41 var $x_axis_position = ''; // Where to draw both axis (world coordinates), 42 var $y_axis_position = ''; // leave blank for X axis at 0 and Y axis at left of plot. 43 44 var $xscale_type = 'linear'; // linear, log 45 var $yscale_type = 'linear'; 46 47//Fonts 48 var $use_ttf = FALSE; // Use True Type Fonts? 49 var $ttf_path = '.'; // Default path to look in for TT Fonts. 50 var $default_ttfont = 'benjamingothic.ttf'; 51 var $line_spacing = 4; // Pixels between lines. 52 53 // Font angles: 0 or 90 degrees for fixed fonts, any for TTF 54 var $x_label_angle = 0; // For labels on X axis (tick and data) 55 var $y_label_angle = 0; // For labels on Y axis (tick and data) 56 var $x_title_angle = 0; // Don't change this if you don't want to screw things up! 57 var $y_title_angle = 90; // Nor this. 58 var $title_angle = 0; // Or this. 59 60//Formats 61 var $file_format = 'png'; 62 var $output_file = ''; // For output to a file instead of stdout 63 64//Data 65 var $data_type = 'text-data'; // text-data, data-data-error, data-data, text-data-single 66 var $plot_type= 'linepoints'; // bars, lines, linepoints, area, points, pie, thinbarline, squared 67 68 var $label_scale_position = 0.5; // Shifts data labes in pie charts. 1 = top, 0 = bottom 69 var $group_frac_width = 0.7; // Bars use this fraction (0 to 1) of a group's space 70 var $bar_extra_space = 0.5; // Number of extra bar's worth of space in a group 71 var $bar_width_adjust = 1; // 1 = bars of normal width, must be > 0 72 73 var $y_precision = 1; 74 var $x_precision = 1; 75 76 var $data_units_text = ''; // Units text for 'data' labels (i.e: '�', '$', etc.) 77 78// Titles 79 var $title_txt = ''; 80 81 var $x_title_txt = ''; 82 var $x_title_pos = 'plotdown'; // plotdown, plotup, both, none 83 84 var $y_title_txt = ''; 85 var $y_title_pos = 'plotleft'; // plotleft, plotright, both, none 86 87 88//Labels 89 // There are two types of labels in PHPlot: 90 // Tick labels: they follow the grid, next to ticks in axis. (DONE) 91 // they are drawn at grid drawing time, by DrawXTicks() and DrawYTicks() 92 // Data labels: they follow the data points, and can be placed on the axis or the plot (x/y) (TODO) 93 // they are drawn at graph plotting time, by Draw*DataLabel(), called by DrawLines(), etc. 94 // Draw*DataLabel() also draws H/V lines to datapoints depending on draw_*_data_label_lines 95 96 // Tick Labels 97 var $x_tick_label_pos = 'plotdown'; // plotdown, plotup, both, xaxis, none 98 var $y_tick_label_pos = 'plotleft'; // plotleft, plotright, both, yaxis, none 99 100 // Data Labels: 101 var $x_data_label_pos = 'plotdown'; // plotdown, plotup, both, plot, all, none 102 var $y_data_label_pos = 'plotleft'; // plotleft, plotright, both, plot, all, plotin, none 103 104 var $draw_x_data_label_lines = FALSE; // Draw a line from the data point to the axis? 105 var $draw_y_data_label_lines = FALSE; // TODO 106 107 // Label types: (for tick, data and plot labels) 108 var $x_label_type = ''; // data, time. Leave blank for no formatting. 109 var $y_label_type = ''; // data, time. Leave blank for no formatting. 110 var $x_time_format = '%H:%M:%S'; // See http://www.php.net/manual/html/function.strftime.html 111 var $y_time_format = '%H:%M:%S'; // SetYTimeFormat() too... 112 113 // Skipping labels 114 var $x_label_inc = 1; // Draw a label every this many (1 = all) (TODO) 115 var $y_label_inc = 1; 116 var $_x_label_cnt = 0; // internal count FIXME: work in progress 117 118 // Legend 119 var $legend = ''; // An array with legend titles 120 var $legend_x_pos = ''; 121 var $legend_y_pos = ''; 122 123 124//Ticks 125 var $x_tick_length = 5; // tick length in pixels for upper/lower axis 126 var $y_tick_length = 5; // tick length in pixels for left/right axis 127 128 var $x_tick_cross = 3; // ticks cross x axis this many pixels 129 var $y_tick_cross = 3; // ticks cross y axis this many pixels 130 131 var $x_tick_pos = 'plotdown'; // plotdown, plotup, both, xaxis, none 132 var $y_tick_pos = 'plotleft'; // plotright, plotleft, both, yaxis, none 133 134 var $num_x_ticks = ''; 135 var $num_y_ticks = ''; 136 137 var $x_tick_inc = ''; // Set num_x_ticks or x_tick_inc, not both. 138 var $y_tick_inc = ''; // Set num_y_ticks or y_tick_inc, not both. 139 140 var $skip_top_tick = FALSE; 141 var $skip_bottom_tick = FALSE; 142 var $skip_left_tick = FALSE; 143 var $skip_right_tick = FALSE; 144 145//Grid Formatting 146 var $draw_x_grid = FALSE; 147 var $draw_y_grid = TRUE; 148 149 var $dashed_grid = TRUE; 150 var $grid_at_foreground = FALSE; // Chooses whether to draw the grid below or above the graph 151 152//Colors and styles (all colors can be array (R,G,B) or named color) 153 var $color_array = 'small'; // 'small', 'large' or array (define your own colors) 154 // See rgb.inc.php and SetRGBArray() 155 var $i_border = array(194, 194, 194); 156 var $plot_bg_color = 'white'; 157 var $bg_color = 'white'; 158 var $label_color = 'black'; 159 var $text_color = 'black'; 160 var $grid_color = 'black'; 161 var $light_grid_color = 'gray'; 162 var $tick_color = 'black'; 163 var $title_color = 'black'; 164 var $data_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1'); 165 var $error_bar_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1'); 166 var $data_border_colors = array('black'); 167 168 var $line_widths = 1; // single value or array 169 var $line_styles = array('solid', 'solid', 'dashed'); // single value or array 170 var $dashed_style = '2-4'; // colored dots-transparent dots 171 172 var $point_sizes = array(5,5,3); // single value or array 173 var $point_shapes = array('diamond'); // rect, circle, diamond, triangle, dot, line, halfline, cross 174 175 var $error_bar_size = 5; // right and left size of tee 176 var $error_bar_shape = 'tee'; // 'tee' or 'line' 177 var $error_bar_line_width = 1; // single value (or array TODO) 178 179 var $plot_border_type = 'sides'; // left, sides, none, full 180 var $image_border_type = 'none'; // 'raised', 'plain', 'none' 181 182 var $shading = 5; // 0 for no shading, > 0 is size of shadows in pixels 183 184 var $draw_plot_area_background = FALSE; 185 var $draw_broken_lines = FALSE; // Tells not to draw lines for missing Y data. 186 187 188////////////////////////////////////////////////////// 189//BEGIN CODE 190////////////////////////////////////////////////////// 191 192 /*! 193 * Constructor: Setup img resource, colors and size of the image, and font sizes. 194 * 195 * \param which_width int Image width in pixels. 196 * \param which_height int Image height in pixels. 197 * \param which_output_file string Filename for output. 198 * \param which_input_fule string Path to a file to be used as background. 199 */ 200 function PHPlot($which_width=600, $which_height=400, $which_output_file=NULL, $which_input_file=NULL) 201 { 202 /* 203 * Please see http://www.php.net/register_shutdown_function 204 * PLEASE NOTE: register_shutdown_function() will take a copy of the object rather than a reference 205 * so we put an ampersand. However, the function registered will work on the object as it 206 * was upon registration. To solve this, one of two methods can be used: 207 * $obj = new object(); 208 * register_shutdown_function(array(&$obj,'shutdown')); 209 * OR 210 * $obj = &new object(); 211 * HOWEVER, as the second statement assigns $obj a reference to the current object, it might be that 212 * several instances mess things up... (CHECK THIS) 213 * 214 * AND 215 * as $this->img is set upon construction of the object, problems will not arise for us (for the 216 * moment maybe, so I put all this here just in case) 217 */ 218 register_shutdown_function(array(&$this, '_PHPlot')); 219 220 $this->SetRGBArray($this->color_array); 221 222 $this->background_done = FALSE; // Set to TRUE after background image is drawn once 223 224 if ($which_output_file) 225 $this->SetOutputFile($which_output_file); 226 227 if ($which_input_file) 228 $this->SetInputFile($which_input_file); 229 else { 230 $this->image_width = $which_width; 231 $this->image_height = $which_height; 232 233 $this->img = ImageCreate($this->image_width, $this->image_height); 234 if (! $this->img) 235 $this->PrintError('PHPlot(): Could not create image resource.'); 236 237 } 238 239 $this->SetDefaultStyles(); 240 $this->SetDefaultFonts(); 241 242 $this->SetTitle(''); 243 $this->SetXTitle(''); 244 $this->SetYTitle(''); 245 246 $this->print_image = TRUE; // Use for multiple plots per image (TODO: automatic) 247 } 248 249 /*! 250 * Destructor. Image resources not deallocated can be memory hogs, I think 251 * it is safer to automatically call imagedestroy upon script termination than 252 * do it ourselves. 253 * See notes in the constructor code. 254 */ 255 function _PHPlot () 256 { 257 ImageDestroy($this->img); 258 return; 259 } 260 261 262///////////////////////////////////////////// 263////////////// COLORS 264///////////////////////////////////////////// 265 266 /*! 267 * Returns an index to a color passed in as anything (string, hex, rgb) 268 * 269 * \param which_color * Color (can be '#AABBCC', 'Colorname', or array(r,g,b)) 270 */ 271 function SetIndexColor($which_color) 272 { 273 list ($r, $g, $b) = $this->SetRGBColor($which_color); //Translate to RGB 274 $index = ImageColorExact($this->img, $r, $g, $b); 275 if ($index == -1) { 276 return ImageColorResolve($this->img, $r, $g, $b); 277 } else { 278 return $index; 279 } 280 } 281 282 283 /*! 284 * Returns an index to a slightly darker color than the one requested. 285 */ 286 function SetIndexDarkColor($which_color) 287 { 288 list ($r, $g, $b) = $this->SetRGBColor($which_color); 289 290 $r -= 0x30; $r = ($r < 0) ? 0 : $r; 291 $g -= 0x30; $g = ($g < 0) ? 0 : $g; 292 $b -= 0x30; $b = ($b < 0) ? 0 : $b; 293 294 $index = ImageColorExact($this->img, $r, $g, $b); 295 if ($index == -1) { 296 return ImageColorResolve($this->img, $r, $g, $b); 297 } else { 298 return $index; 299 } 300 } 301 302 /*! 303 * Sets/reverts all colors and styles to their defaults. If session is set, then only updates indices, 304 * as they are lost with every script execution, else, sets the default colors by name or value and 305 * then updates indices too. 306 * 307 * FIXME Isn't this too slow? 308 * 309 */ 310 function SetDefaultStyles() 311 { 312 /* Some of the Set*() functions use default values when they get no parameters. */ 313 314 if (! isset($this->session_set)) { 315 // If sessions are enabled, this variable will be preserved, so upon future executions, we 316 // will have it set, as well as color names (though not color indices, that's why we 317 // need to rebuild them) 318 $this->session_set = TRUE; 319 320 // These only need to be set once 321 $this->SetLineWidths(); 322 $this->SetLineStyles(); 323 $this->SetDefaultDashedStyle($this->dashed_style); 324 $this->SetPointSizes($this->point_sizes); 325 } 326 327 $this->SetImageBorderColor($this->i_border); 328 $this->SetPlotBgColor($this->plot_bg_color); 329 $this->SetBackgroundColor($this->bg_color); 330 $this->SetLabelColor($this->label_color); 331 $this->SetTextColor($this->text_color); 332 $this->SetGridColor($this->grid_color); 333 $this->SetLightGridColor($this->light_grid_color); 334 $this->SetTickColor($this->tick_color); 335 $this->SetTitleColor($this->title_color); 336 $this->SetDataColors(); 337 $this->SetErrorBarColors(); 338 $this->SetDataBorderColors(); 339 } 340 341 342 /* 343 * 344 */ 345 function SetBackgroundColor($which_color) 346 { 347 $this->bg_color= $which_color; 348 $this->ndx_bg_color= $this->SetIndexColor($this->bg_color); 349 return TRUE; 350 } 351 352 /* 353 * 354 */ 355 function SetPlotBgColor($which_color) 356 { 357 $this->plot_bg_color= $which_color; 358 $this->ndx_plot_bg_color= $this->SetIndexColor($this->plot_bg_color); 359 return TRUE; 360 } 361 362 /* 363 * 364 */ 365 function SetTitleColor($which_color) 366 { 367 $this->title_color= $which_color; 368 $this->ndx_title_color= $this->SetIndexColor($this->title_color); 369 return TRUE; 370 } 371 372 /* 373 * 374 */ 375 function SetTickColor ($which_color) 376 { 377 $this->tick_color= $which_color; 378 $this->ndx_tick_color= $this->SetIndexColor($this->tick_color); 379 return TRUE; 380 } 381 382 383 /* 384 * 385 */ 386 function SetLabelColor ($which_color) 387 { 388 $this->label_color = $which_color; 389 $this->ndx_title_color= $this->SetIndexColor($this->label_color); 390 return TRUE; 391 } 392 393 394 /* 395 * 396 */ 397 function SetTextColor ($which_color) 398 { 399 $this->text_color= $which_color; 400 $this->ndx_text_color= $this->SetIndexColor($this->text_color); 401 return TRUE; 402 } 403 404 405 /* 406 * 407 */ 408 function SetLightGridColor ($which_color) 409 { 410 $this->light_grid_color= $which_color; 411 $this->ndx_light_grid_color= $this->SetIndexColor($this->light_grid_color); 412 return TRUE; 413 } 414 415 416 /* 417 * 418 */ 419 function SetGridColor ($which_color) 420 { 421 $this->grid_color = $which_color; 422 $this->ndx_grid_color= $this->SetIndexColor($this->grid_color); 423 return TRUE; 424 } 425 426 427 /* 428 * 429 */ 430 function SetImageBorderColor($which_color) 431 { 432 $this->i_border = $which_color; 433 $this->ndx_i_border = $this->SetIndexColor($this->i_border); 434 $this->ndx_i_border_dark = $this->SetIndexDarkColor($this->i_border); 435 return TRUE; 436 } 437 438 439 /* 440 * 441 */ 442 function SetTransparentColor($which_color) 443 { 444 ImageColorTransparent($this->img, $this->SetIndexColor($which_color)); 445 return TRUE; 446 } 447 448 449 /*! 450 * Sets the array of colors to be used. It can be user defined, a small predefined one 451 * or a large one included from 'rgb.inc.php'. 452 * 453 * \param which_color_array If an array, the used as color array. If a string can 454 * be one of 'small' or 'large'. 455 */ 456 function SetRGBArray ($which_color_array) 457 { 458 if ( is_array($which_color_array) ) { // User defined array 459 $this->rgb_array = $which_color_array; 460 return TRUE; 461 } elseif ($which_color_array == 'small') { // Small predefined color array 462 $this->rgb_array = array( 463 'white' => array(255, 255, 255), 464 'snow' => array(255, 250, 250), 465 'PeachPuff' => array(255, 218, 185), 466 'ivory' => array(255, 255, 240), 467 'lavender' => array(230, 230, 250), 468 'black' => array( 0, 0, 0), 469 'DimGrey' => array(105, 105, 105), 470 'gray' => array(190, 190, 190), 471 'grey' => array(190, 190, 190), 472 'navy' => array( 0, 0, 128), 473 'SlateBlue' => array(106, 90, 205), 474 'blue' => array( 0, 0, 255), 475 'SkyBlue' => array(135, 206, 235), 476 'cyan' => array( 0, 255, 255), 477 'DarkGreen' => array( 0, 100, 0), 478 'green' => array( 0, 255, 0), 479 'YellowGreen' => array(154, 205, 50), 480 'yellow' => array(255, 255, 0), 481 'orange' => array(255, 165, 0), 482 'gold' => array(255, 215, 0), 483 'peru' => array(205, 133, 63), 484 'beige' => array(245, 245, 220), 485 'wheat' => array(245, 222, 179), 486 'tan' => array(210, 180, 140), 487 'brown' => array(165, 42, 42), 488 'salmon' => array(250, 128, 114), 489 'red' => array(255, 0, 0), 490 'pink' => array(255, 192, 203), 491 'maroon' => array(176, 48, 96), 492 'magenta' => array(255, 0, 255), 493 'violet' => array(238, 130, 238), 494 'plum' => array(221, 160, 221), 495 'orchid' => array(218, 112, 214), 496 'purple' => array(160, 32, 240), 497 'azure1' => array(240, 255, 255), 498 'aquamarine1' => array(127, 255, 212) 499 ); 500 return TRUE; 501 } elseif ($which_color_array === 'large') { // Large color array 502 include("./rgb.inc.php"); 503 $this->rgb_array = $RGBArray; 504 } else { // Default to black and white only. 505 $this->rgb_array = array('white' => array(255, 255, 255), 'black' => array(0, 0, 0)); 506 } 507 508 return TRUE; 509 } 510 511 /*! 512 * Returns an array in R, G, B format 0-255 513 * 514 * \param color_asked array(R,G,B) or string (named color or '#AABBCC') 515 */ 516 function SetRGBColor($color_asked) 517 { 518 if ($color_asked == '') { $color_asked = array(0, 0, 0); }; 519 520 if ( count($color_asked) == 3 ) { // already array of 3 rgb 521 $ret_val = $color_asked; 522 } else { // asking for a color by string 523 if(substr($color_asked, 0, 1) == '#') { // asking in #FFFFFF format. 524 $ret_val = array(hexdec(substr($color_asked, 1, 2)), hexdec(substr($color_asked, 3, 2)), 525 hexdec(substr($color_asked, 5, 2))); 526 } else { // asking by color name 527 $ret_val = $this->rgb_array[$color_asked]; 528 } 529 } 530 return $ret_val; 531 } 532 533 534 /*! 535 * Sets the colors for the data. 536 */ 537 function SetDataColors($which_data = NULL, $which_border = NULL) 538 { 539 if (is_null($which_data) && is_array($this->data_colors)) { 540 // use already set data_colors 541 } else if (! is_array($which_data)) { 542 $this->data_colors = ($which_data) ? array($which_data) : array('blue', 'red', 'green', 'orange'); 543 } else { 544 $this->data_colors = $which_data; 545 } 546 547 $i = 0; 548 foreach ($this->data_colors as $col) { 549 $this->ndx_data_colors[$i] = $this->SetIndexColor($col); 550 $this->ndx_data_dark_colors[$i] = $this->SetIndexDarkColor($col); 551 $i++; 552 } 553 554 // For past compatibility: 555 $this->SetDataBorderColors($which_border); 556 } // function SetDataColors() 557 558 559 /*! 560 * 561 */ 562 function SetDataBorderColors($which_br = NULL) 563 { 564 if (is_null($which_br) && is_array($this->data_border_colors)) { 565 // use already set data_border_colors 566 } else if (! is_array($which_br)) { 567 // Create new array with specified color 568 $this->data_border_colors = ($which_br) ? array($which_br) : array('black'); 569 } else { 570 $this->data_border_colors = $which_br; 571 } 572 573 $i = 0; 574 foreach($this->data_border_colors as $col) { 575 $this->ndx_data_border_colors[$i] = $this->SetIndexColor($col); 576 $i++; 577 } 578 } // function SetDataBorderColors() 579 580 581 /*! 582 * Sets the colors for the data error bars. 583 */ 584 function SetErrorBarColors($which_err = NULL) 585 { 586 if (is_null($which_err) && is_array($this->error_bar_colors)) { 587 // use already set error_bar_colors 588 } else if (! is_array($which_err)) { 589 $this->error_bar_colors = ($which_err) ? array($which_err) : array('black'); 590 } else { 591 $this->error_bar_colors = $which_err; 592 } 593 594 $i = 0; 595 foreach($this->error_bar_colors as $col) { 596 $this->ndx_error_bar_colors[$i] = $this->SetIndexColor($col); 597 $i++; 598 } 599 return TRUE; 600 601 } // function SetErrorBarColors() 602 603 604 /*! 605 * Sets the default dashed style. 606 * \param which_style A string specifying order of colored and transparent dots, 607 * i.e: '4-3' means 4 colored, 3 transparent; 608 * '2-3-1-2' means 2 colored, 3 transparent, 1 colored, 2 transparent. 609 */ 610 function SetDefaultDashedStyle($which_style) 611 { 612 // String: "numcol-numtrans-numcol-numtrans..." 613 $asked = explode('-', $which_style); 614 615 if (count($asked) < 2) { 616 $this->DrawError("SetDefaultDashedStyle(): Wrong parameter '$which_style'."); 617 return FALSE; 618 } 619 620 // Build the string to be eval()uated later by SetDashedStyle() 621 $this->default_dashed_style = 'array( '; 622 623 $t = 0; 624 foreach($asked as $s) { 625 if ($t % 2 == 0) { 626 $this->default_dashed_style .= str_repeat('$which_ndxcol,', $s); 627 } else { 628 $this->default_dashed_style .= str_repeat('IMG_COLOR_TRANSPARENT,', $s); 629 } 630 $t++; 631 } 632 // Remove trailing comma and add closing parenthesis 633 $this->default_dashed_style = substr($this->default_dashed_style, 0, -1); 634 $this->default_dashed_style .= ')'; 635 636 return TRUE; 637 } 638 639 640 /*! 641 * Sets the style before drawing a dashed line. Defaults to $this->default_dashed_style 642 * \param which_ndxcol Color index to be used. 643 */ 644 function SetDashedStyle($which_ndxcol) 645 { 646 // See SetDefaultDashedStyle() to understand this. 647 eval ("\$style = $this->default_dashed_style;"); 648 return imagesetstyle($this->img, $style); 649 } 650 651 652 /*! 653 * Sets line widths on a per-line basis. 654 */ 655 function SetLineWidths($which_lw=NULL) 656 { 657 if (is_null($which_lw)) { 658 // Do nothing, use default value. 659 } else if (is_array($which_lw)) { 660 // Did we get an array with line widths? 661 $this->line_widths = $which_lw; 662 } else { 663 $this->line_widths = array($which_lw); 664 } 665 return TRUE; 666 } 667 668 /*! 669 * 670 */ 671 function SetLineStyles($which_ls=NULL) 672 { 673 if (is_null($which_ls)) { 674 // Do nothing, use default value. 675 } else if ( is_array($which_ls)) { 676 // Did we get an array with line styles? 677 $this->line_styles = $which_ls; 678 } else { 679 $this->line_styles = ($which_ls) ? array($which_ls) : array('solid'); 680 } 681 return TRUE; 682 } 683 684 685///////////////////////////////////////////// 686////////////// FONTS 687///////////////////////////////////////////// 688 689 690 /*! 691 * Sets number of pixels between lines of the same text. 692 */ 693 function SetLineSpacing($which_spc) 694 { 695 $this->line_spacing = $which_spc; 696 } 697 698 699 /*! 700 * Enables use of TrueType fonts in the graph. Font initialisation methods 701 * depend on this setting, so when called, SetUseTTF() resets the font 702 * settings 703 */ 704 function SetUseTTF($which_ttf) 705 { 706 $this->use_ttf = $which_ttf; 707 $this->SetDefaultFonts(); 708 return TRUE; 709 } 710 711 /*! 712 * Sets the directory name to look into for TrueType fonts. 713 */ 714 function SetTTFPath($which_path) 715 { 716 // Maybe someone needs really dynamic config. He'll need this: 717 // clearstatcache(); 718 719 if (is_dir($which_path) && is_readable($which_path)) { 720 $this->ttf_path = $which_path; 721 return TRUE; 722 } else { 723 $this->PrintError("SetTTFPath(): $which_path is not a valid path."); 724 return FALSE; 725 } 726 } 727 728 /*! 729 * Sets the default TrueType font and updates all fonts to that. 730 * The default font might be a full path, or relative to the TTFPath, 731 * so let SetFont check that it exists. 732 * Side effect: enable use of TrueType fonts. 733 */ 734 function SetDefaultTTFont($which_font) 735 { 736 $this->default_ttfont = $which_font; 737 $this->SetUseTTF(TRUE); 738 return TRUE; 739 } 740 741 /*! 742 * Sets fonts to their defaults 743 */ 744 function SetDefaultFonts() 745 { 746 // TTF: 747 if ($this->use_ttf) { 748 $this->SetFont('generic', '', 8); 749 $this->SetFont('title', '', 14); 750 $this->SetFont('legend', '', 8); 751 $this->SetFont('x_label', '', 6); 752 $this->SetFont('y_label', '', 6); 753 $this->SetFont('x_title', '', 10); 754 $this->SetFont('y_title', '', 10); 755 } 756 // Fixed: 757 else { 758 $this->SetFont('generic', 2); 759 $this->SetFont('title', 5); 760 $this->SetFont('legend', 2); 761 $this->SetFont('x_label', 1); 762 $this->SetFont('y_label', 1); 763 $this->SetFont('x_title', 3); 764 $this->SetFont('y_title', 3); 765 } 766 767 return TRUE; 768 } 769 770 /*! 771 * Sets Fixed/Truetype font parameters. 772 * \param $which_elem Is the element whose font is to be changed. 773 * It can be one of 'title', 'legend', 'generic', 774 * 'x_label', 'y_label', x_title' or 'y_title' 775 * \param $which_font Can be a number (for fixed font sizes) or 776 * a string with the font pathname or filename when using TTFonts. 777 * For TTFonts, an empty string means use the default font. 778 * \param $which_size Point size (TTF only) 779 * Calculates and updates internal height and width variables. 780 */ 781 function SetFont($which_elem, $which_font, $which_size = 12) 782 { 783 // TTF: 784 if ($this->use_ttf) { 785 // Empty font name means use the default font. 786 if (empty($which_font)) 787 $which_font = $this->default_ttfont; 788 $path = $which_font; 789 790 // First try the font name directly, if not then try with path. 791 if (!is_file($path) || !is_readable($path)) { 792 $path = $this->ttf_path . '/' . $which_font; 793 if (!is_file($path) || !is_readable($path)) { 794 $this->DrawError("SetFont(): Can't find TrueType font $which_font"); 795 return FALSE; 796 } 797 } 798 799 switch ($which_elem) { 800 case 'generic': 801 $this->generic_font['font'] = $path; 802 $this->generic_font['size'] = $which_size; 803 break; 804 case 'title': 805 $this->title_font['font'] = $path; 806 $this->title_font['size'] = $which_size; 807 break; 808 case 'legend': 809 $this->legend_font['font'] = $path; 810 $this->legend_font['size'] = $which_size; 811 break; 812 case 'x_label': 813 $this->x_label_font['font'] = $path; 814 $this->x_label_font['size'] = $which_size; 815 break; 816 case 'y_label': 817 $this->y_label_font['font'] = $path; 818 $this->y_label_font['size'] = $which_size; 819 break; 820 case 'x_title': 821 $this->x_title_font['font'] = $path; 822 $this->x_title_font['size'] = $which_size; 823 break; 824 case 'y_title': 825 $this->y_title_font['font'] = $path; 826 $this->y_title_font['size'] = $which_size; 827 break; 828 default: 829 $this->DrawError("SetFont(): Unknown element '$which_elem' specified."); 830 return FALSE; 831 } 832 return TRUE; 833 834 } 835 836 // Fixed fonts: 837 if ($which_font > 5 || $which_font < 0) { 838 $this->DrawError('SetFont(): Non-TTF font size must be 1, 2, 3, 4 or 5'); 839 return FALSE; 840 } 841 842 switch ($which_elem) { 843 case 'generic': 844 $this->generic_font['font'] = $which_font; 845 $this->generic_font['height'] = ImageFontHeight($which_font); 846 $this->generic_font['width'] = ImageFontWidth($which_font); 847 break; 848 case 'title': 849 $this->title_font['font'] = $which_font; 850 $this->title_font['height'] = ImageFontHeight($which_font); 851 $this->title_font['width'] = ImageFontWidth($which_font); 852 break; 853 case 'legend': 854 $this->legend_font['font'] = $which_font; 855 $this->legend_font['height'] = ImageFontHeight($which_font); 856 $this->legend_font['width'] = ImageFontWidth($which_font); 857 break; 858 case 'x_label': 859 $this->x_label_font['font'] = $which_font; 860 $this->x_label_font['height'] = ImageFontHeight($which_font); 861 $this->x_label_font['width'] = ImageFontWidth($which_font); 862 break; 863 case 'y_label': 864 $this->y_label_font['font'] = $which_font; 865 $this->y_label_font['height'] = ImageFontHeight($which_font); 866 $this->y_label_font['width'] = ImageFontWidth($which_font); 867 break; 868 case 'x_title': 869 $this->x_title_font['font'] = $which_font; 870 $this->x_title_font['height'] = ImageFontHeight($which_font); 871 $this->x_title_font['width'] = ImageFontWidth($which_font); 872 break; 873 case 'y_title': 874 $this->y_title_font['font'] = $which_font; 875 $this->y_title_font['height'] = ImageFontHeight($which_font); 876 $this->y_title_font['width'] = ImageFontWidth($which_font); 877 break; 878 default: 879 $this->DrawError("SetFont(): Unknown element '$which_elem' specified."); 880 return FALSE; 881 } 882 return TRUE; 883 } 884 885 886 /*! 887 * Returns an array with the size of the bounding box of an 888 * arbitrarily placed (rotated) TrueType text string. 889 */ 890 function TTFBBoxSize($size, $angle, $font, $string) 891 { 892 // First, assume angle < 90 893 $arr = ImageTTFBBox($size, 0, $font, $string); 894 $flat_width = $arr[2] - $arr[0]; 895 $flat_height = abs($arr[3] - $arr[5]); 896 897 // Now the bounding box 898 $angle = deg2rad($angle); 899 $width = ceil(abs($flat_width*cos($angle) + $flat_height*sin($angle))); //Must be integer 900 $height = ceil(abs($flat_width*sin($angle) + $flat_height*cos($angle))); //Must be integer 901 902 return array($width, $height); 903 } 904 905 906 /*! 907 * Draws a string of text. Horizontal and vertical alignment are relative to 908 * to the drawing. That is: vertical text (90 deg) gets centered along y-axis 909 * with v_align = 'center', and adjusted to the left of x-axis with h_align = 'right', 910 * 911 * \note Original multiple lines code submitted by Remi Ricard. 912 * \note Original vertical code submitted by Marlin Viss. 913 */ 914 function DrawText($which_font, $which_angle, $which_xpos, $which_ypos, $which_color, $which_text, 915 $which_halign = 'left', $which_valign = 'bottom') 916 { 917 // TTF: 918 if ($this->use_ttf) { 919 $size = $this->TTFBBoxSize($which_font['size'], $which_angle, $which_font['font'], $which_text); 920 $rads = deg2rad($which_angle); 921 922 if ($which_valign == 'center') 923 $which_ypos += $size[1]/2; 924 925 if ($which_valign == 'bottom') 926 $which_ypos += $size[1]; 927 928 if ($which_halign == 'center') 929 $which_xpos -= ($size[0]/2) * cos($rads); 930 931 if ($which_halign == 'left') 932 $which_xpos += $size[0] * sin($rads); 933 934 if ($which_halign == 'right') 935 $which_xpos -= $size[0] * cos($rads); 936 937 ImageTTFText($this->img, $which_font['size'], $which_angle, 938 $which_xpos, $which_ypos, $which_color, $which_font['font'], $which_text); 939 } 940 // Fixed fonts: 941 else { 942 // Split the text by its lines, and count them 943 $which_text = ereg_replace("\r", "", $which_text); 944 $str = split("\n", $which_text); 945 $nlines = count($str); 946 $spacing = $this->line_spacing * ($nlines - 1); 947 948 // Vertical text: 949 // (Remember the alignment convention with vertical text) 950 if ($which_angle == 90) { 951 // The text goes around $which_xpos. 952 if ($which_halign == 'center') 953 $which_xpos -= ($nlines * ($which_font['height'] + $spacing))/2; 954 955 // Left alignment requires no modification to $xpos... 956 // Right-align it. $which_xpos designated the rightmost x coordinate. 957 else if ($which_halign == 'right') 958 $which_xpos += ($nlines * ($which_font['height'] + $spacing)); 959 960 $ypos = $which_ypos; 961 for($i = 0; $i < $nlines; $i++) { 962 // Center the text vertically around $which_ypos (each line) 963 if ($which_valign == 'center') 964 $ypos = $which_ypos + (strlen($str[$i]) * $which_font['width']) / 2; 965 // Make the text finish (vertically) at $which_ypos 966 if ($which_valign == 'bottom') 967 $ypos = $which_ypos + strlen($str[$i]) * $which_font['width']; 968 969 ImageStringUp($this->img, $which_font['font'], 970 $i * ($which_font['height'] + $spacing) + $which_xpos, 971 $ypos, $str[$i], $which_color); 972 } 973 } 974 // Horizontal text: 975 else { 976 // The text goes above $which_ypos 977 if ($which_valign == 'top') 978 $which_ypos -= $nlines * ($which_font['height'] + $spacing); 979 // The text is centered around $which_ypos 980 if ($which_valign == 'center') 981 $which_ypos -= ($nlines * ($which_font['height'] + $spacing))/2; 982 // valign = 'bottom' requires no modification 983 984 $xpos = $which_xpos; 985 for($i = 0; $i < $nlines; $i++) { 986 // center the text around $which_xpos 987 if ($which_halign == 'center') 988 $xpos = $which_xpos - (strlen($str[$i]) * $which_font['width'])/2; 989 // make the text finish at $which_xpos 990 if ($which_halign == 'right') 991 $xpos = $which_xpos - strlen($str[$i]) * $which_font['width']; 992 993 ImageString($this->img, $which_font['font'], $xpos, 994 $i * ($which_font['height'] + $spacing) + $which_ypos, 995 $str[$i], $which_color); 996 } 997 } 998 } 999 return TRUE; 1000 } // function DrawText() 1001 1002 1003///////////////////////////////////////////// 1004/////////// INPUT / OUTPUT CONTROL 1005///////////////////////////////////////////// 1006 1007 /*! 1008 * Sets output file format. 1009 */ 1010 function SetFileFormat($format) 1011 { 1012 $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__); 1013 1014 switch ($asked) { 1015 case 'jpg': 1016 if (imagetypes() & IMG_JPG) 1017 $this->file_format = 'jpg'; 1018 return TRUE; 1019 break; 1020 case 'png': 1021 if (imagetypes() & IMG_PNG) 1022 $this->file_format = 'png'; 1023 return TRUE; 1024 break; 1025 case 'gif': 1026 if (imagetypes() & IMG_GIF) 1027 $this->file_format = 'gif'; 1028 return TRUE; 1029 break; 1030 case 'wbmp': 1031 if (imagetypes() & IMG_WBMP) 1032 $this->file_format = 'wbmp'; 1033 return TRUE; 1034 break; 1035 default: 1036 $this->PrintError("SetFileFormat():File format '$format' not supported"); 1037 return FALSE; 1038 } 1039 } 1040 1041 1042 /*! 1043 * Selects an input file to be used as graph background and scales or tiles this image 1044 * to fit the sizes. 1045 * \param input_file string Path to the file to be used (jpeg, png and gif accepted) 1046 * \param mode string 'centeredtile', 'tile', 'scale' (the image to the graph's size) 1047 */ 1048 function SetBgImage($input_file, $mode='centeredtile') 1049 { 1050 $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__); 1051 $this->bgimg = $input_file; 1052 } 1053 1054 /*! 1055 * Selects an input file to be used as plot area background and scales or tiles this image 1056 * to fit the sizes. 1057 * \param input_file string Path to the file to be used (jpeg, png and gif accepted) 1058 * \param mode string 'centeredtile', 'tile', 'scale' (the image to the graph's size) 1059 */ 1060 function SetPlotAreaBgImage($input_file, $mode='tile') 1061 { 1062 $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__); 1063 $this->plotbgimg = $input_file; 1064 } 1065 1066 1067 /*! 1068 * Sets the name of the file to be used as output file. 1069 */ 1070 function SetOutputFile($which_output_file) 1071 { 1072 $this->output_file = $which_output_file; 1073 return TRUE; 1074 } 1075 1076 /*! 1077 * Sets the output image as 'inline', that is: no Content-Type headers are sent 1078 * to the browser. Needed if you want to embed the images. 1079 */ 1080 function SetIsInline($which_ii) 1081 { 1082 $this->is_inline = (bool)$which_ii; 1083 return TRUE; 1084 } 1085 1086 1087 /*! 1088 * Performs the actual outputting of the generated graph, and 1089 * destroys the image resource. 1090 */ 1091 function PrintImage() 1092 { 1093 // Browser cache stuff submitted by Thiemo Nagel 1094 if ( (! $this->browser_cache) && (! $this->is_inline)) { 1095 header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 1096 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); 1097 header('Cache-Control: no-cache, must-revalidate'); 1098 header('Pragma: no-cache'); 1099 } 1100 1101 switch($this->file_format) { 1102 case 'png': 1103 if (! $this->is_inline) { 1104 Header('Content-type: image/png'); 1105 } 1106 if ($this->is_inline && $this->output_file != '') { 1107 ImagePng($this->img, $this->output_file); 1108 } else { 1109 ImagePng($this->img); 1110 } 1111 break; 1112 case 'jpg': 1113 if (! $this->is_inline) { 1114 Header('Content-type: image/jpeg'); 1115 } 1116 if ($this->is_inline && $this->output_file != '') { 1117 ImageJPEG($this->img, $this->output_file); 1118 } else { 1119 ImageJPEG($this->img); 1120 } 1121 break; 1122 case 'gif': 1123 if (! $this->is_inline) { 1124 Header('Content-type: image/gif'); 1125 } 1126 if ($this->is_inline && $this->output_file != '') { 1127 ImageGIF($this->img, $this->output_file); 1128 } else { 1129 ImageGIF($this->img); 1130 } 1131 1132 break; 1133 case 'wbmp': // wireless bitmap, 2 bit. 1134 if (! $this->is_inline) { 1135 Header('Content-type: image/wbmp'); 1136 } 1137 if ($this->is_inline && $this->output_file != '') { 1138 ImageWBMP($this->img, $this->output_file); 1139 } else { 1140 ImageWBMP($this->img); 1141 } 1142 1143 break; 1144 default: 1145 $this->PrintError('PrintImage(): Please select an image type!'); 1146 break; 1147 } 1148 return TRUE; 1149 } 1150 1151 /*! 1152 * Prints an error message to stdout and dies 1153 */ 1154 function PrintError($error_message) 1155 { 1156 echo "<p><b>Fatal error</b>: $error_message<p>"; 1157 die; 1158 } 1159 1160 /*! 1161 * Prints an error message inline into the generated image and draws it centered 1162 * around the given coordinates (defaults to center of the image) 1163 * \param error_message Message to be drawn 1164 * \param where_x X coordinate 1165 * \param where_y Y coordinate 1166 */ 1167 function DrawError($error_message, $where_x = NULL, $where_y = NULL) 1168 { 1169 if (! $this->img) 1170 $this->PrintError('_DrawError(): Warning, no image resource allocated. '. 1171 'The message to be written was: '.$error_message); 1172 1173 $ypos = (! $where_y) ? $this->image_height/2 : $where_y; 1174 $xpos = (! $where_x) ? $this->image_width/2 : $where_x; 1175 ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, 1176 ImageColorAllocate($this->img, 255, 255, 255)); 1177 1178 // Switch to built-in fonts, in case of error with TrueType fonts: 1179 $this->SetUseTTF(FALSE); 1180 1181 $this->DrawText($this->generic_font, 0, $xpos, $ypos, ImageColorAllocate($this->img, 0, 0, 0), 1182 wordwrap($error_message), 'center', 'center'); 1183 1184 $this->PrintImage(); 1185 exit; 1186// return TRUE; 1187 } 1188 1189///////////////////////////////////////////// 1190/////////// LABELS 1191///////////////////////////////////////////// 1192 1193 1194 /*! 1195 * Sets position for X labels following data points. 1196 */ 1197 function SetXDataLabelPos($which_xdlp) 1198 { 1199 $this->x_data_label_pos = $this->CheckOption($which_xdlp, 'plotdown, plotup, both, xaxis, all, none', 1200 __FUNCTION__); 1201 if ($which_xdlp != 'none') 1202 $this->x_tick_label_pos = 'none'; 1203 1204 return TRUE; 1205 } 1206 1207 /*! 1208 * Sets position for Y labels near data points. 1209 * For past compatability we accept plotleft, ...but pass it to SetTickLabelPos 1210 * eventually to specify how far up/down or left/right of the data point 1211 */ 1212 function SetYDataLabelPos($which_ydlp, $which_distance_from_point=0) 1213 { 1214 $this->y_data_label_pos = $this->CheckOption($which_ydlp, 'plotleft, plotright, both, yaxis, all, plotin, none', 1215 __FUNCTION__); 1216 //This bit in SetYDataLabelPos about plotleft is for those who were 1217 //using this function to set SetYTickLabelPos. 1218 if ( ($which_ydlp == 'plotleft') || ($which_ydlp == 'plotright') || 1219 ($which_ydlp == 'both') || ($which_ydlp == 'yaxis') ) { 1220 1221 //Call sety_TICK_labelpos instead of sety_DATA_labelpos 1222 $this->SetYTickLabelPos($which_ydlp); 1223 1224 } elseif ($which_ydlp != 'none') { 1225 //right now its plotin or none 1226 $this->y_data_label_pos = 'plotin'; 1227 } 1228 1229 return TRUE; 1230 } 1231 1232 1233 /*! 1234 * Sets position for X labels following ticks (hence grid lines) 1235 */ 1236 function SetXTickLabelPos($which_xtlp) 1237 { 1238 $this->x_tick_label_pos = $this->CheckOption($which_xtlp, 'plotdown, plotup, both, xaxis, all, none', 1239 __FUNCTION__); 1240 if ($which_xtlp != 'none') 1241 $this->x_data_label_pos = 'none'; 1242 1243 return TRUE; 1244 } 1245 1246 /*! 1247 * Sets position for Y labels following ticks (hence grid lines) 1248 */ 1249 function SetYTickLabelPos($which_ytlp) 1250 { 1251 $this->y_tick_label_pos = $this->CheckOption($which_ytlp, 'plotleft, plotright, both, yaxis, all, none', 1252 __FUNCTION__); 1253 return TRUE; 1254 } 1255 1256 /*! 1257 * Sets type for tick and data labels on X axis. 1258 * \note 'title' type left for backwards compatibility. 1259 */ 1260 function SetXLabelType($which_xlt) 1261 { 1262 $this->x_label_type = $this->CheckOption($which_xlt, 'data, time, title', __FUNCTION__); 1263 return TRUE; 1264 } 1265 1266 /*! 1267 * Sets type for tick and data labels on Y axis. 1268 */ 1269 function SetYLabelType($which_ylt) 1270 { 1271 $this->y_label_type = $this->CheckOption($which_ylt, 'data, time', __FUNCTION__); 1272 return TRUE; 1273 } 1274 1275 function SetXTimeFormat($which_xtf) 1276 { 1277 $this->x_time_format = $which_xtf; 1278 return TRUE; 1279 } 1280 function SetYTimeFormat($which_ytf) 1281 { 1282 $this->y_time_format = $which_ytf; 1283 return TRUE; 1284 } 1285 1286 function SetXLabelAngle($which_xla) 1287 { 1288 $this->x_label_angle = $which_xla; 1289 return TRUE; 1290 } 1291 1292 function SetYLabelAngle($which_yla) 1293 { 1294 $this->y_label_angle = $which_yla; 1295 return TRUE; 1296 } 1297 1298///////////////////////////////////////////// 1299/////////// MISC 1300///////////////////////////////////////////// 1301 1302 /*! 1303 * Checks the valididy of an option. 1304 * \param which_opt String to check. 1305 * \param which_acc String of accepted choices. 1306 * \param which_func Name of the calling function, for error messages. 1307 * \note If checking everywhere for correctness slows things down, we could provide a 1308 * child class overriding every Set...() method which uses CheckOption(). Those new 1309 * methods could proceed in the unsafe but faster way. 1310 */ 1311 function CheckOption($which_opt, $which_acc, $which_func) 1312 { 1313 $asked = trim($which_opt); 1314 1315 // FIXME: this for backward compatibility, as eregi() fails with empty strings. 1316 if ($asked == '') 1317 return ''; 1318 1319 $asked = strtolower($asked); 1320 if (@ eregi($asked, $which_acc)) { 1321 return $asked; 1322 } else { 1323 $this->DrawError("$which_func(): '$which_opt' not in available choices: '$which_acc'."); 1324 return NULL; 1325 } 1326 } 1327 1328 1329 /*! 1330 * \note Submitted by Thiemo Nagel 1331 */ 1332 function SetBrowserCache($which_browser_cache) 1333 { 1334 $this->browser_cache = $which_browser_cache; 1335 return TRUE; 1336 } 1337 1338 /*! 1339 * Whether to show the final image or not 1340 */ 1341 function SetPrintImage($which_pi) 1342 { 1343 $this->print_image = $which_pi; 1344 return TRUE; 1345 } 1346 1347 /*! 1348 * Sets the graph's legend. If argument is not an array, appends it to the legend. 1349 */ 1350 function SetLegend($which_leg) 1351 { 1352 if (is_array($which_leg)) { // use array 1353 $this->legend = $which_leg; 1354 return TRUE; 1355 } else if (! is_null($which_leg)) { // append string 1356 $this->legend[] = $which_leg; 1357 return TRUE; 1358 } else { 1359 $this->DrawError("SetLegend(): argument must not be null."); 1360 return FALSE; 1361 } 1362 } 1363 1364 /*! 1365 * Specifies the absolute (relative to image's up/left corner) position 1366 * of the legend's upper/leftmost corner. 1367 * $which_type not yet used (TODO) 1368 */ 1369 function SetLegendPixels($which_x, $which_y, $which_type=NULL) 1370 { 1371 $this->legend_x_pos = $which_x; 1372 $this->legend_y_pos = $which_y; 1373 1374 return TRUE; 1375 } 1376 1377 /*! 1378 * Specifies the relative (to graph's origin) position of the legend's 1379 * upper/leftmost corner. MUST be called after scales are set up. 1380 * $which_type not yet used (TODO) 1381 */ 1382 function SetLegendWorld($which_x, $which_y, $which_type=NULL) 1383 { 1384 if (! isset($this->scale_is_set)) 1385 $this->CalcTranslation(); 1386 1387 $this->legend_x_pos = $this->xtr($which_x); 1388 $this->legend_y_pos = $this->ytr($which_y); 1389 1390 return TRUE; 1391 } 1392 1393 /*! 1394 * Accepted values are: left, sides, none, full 1395 */ 1396 function SetPlotBorderType($pbt) 1397 { 1398 $this->plot_border_type = $this->CheckOption($pbt, 'left, sides, none, full', __FUNCTION__); 1399 } 1400 1401 /*! 1402 * Accepted values are: raised, plain 1403 */ 1404 function SetImageBorderType($sibt) 1405 { 1406 $this->image_border_type = $this->CheckOption($sibt, 'raised, plain', __FUNCTION__); 1407 } 1408 1409 1410 /*! 1411 * \param dpab bool 1412 */ 1413 function SetDrawPlotAreaBackground($dpab) 1414 { 1415 $this->draw_plot_area_background = (bool)$dpab; 1416 } 1417 1418 1419 /*! 1420 * \param dyg bool 1421 */ 1422 function SetDrawYGrid($dyg) 1423 { 1424 $this->draw_y_grid = (bool)$dyg; 1425 return TRUE; 1426 } 1427 1428 1429 /*! 1430 * \param dxg bool 1431 */ 1432 function SetDrawXGrid($dxg) 1433 { 1434 $this->draw_x_grid = (bool)$dxg; 1435 return TRUE; 1436 } 1437 1438 1439 /*! 1440 * \param ddg bool 1441 */ 1442 function SetDrawDashedGrid($ddg) 1443 { 1444 $this->dashed_grid = (bool)$ddg; 1445 return TRUE; 1446 } 1447 1448 1449 /*! 1450 * \param dxdl bool 1451 */ 1452 function SetDrawXDataLabelLines($dxdl) 1453 { 1454 $this->draw_x_data_label_lines = (bool)$dxdl; 1455 return TRUE; 1456 } 1457 1458 1459 /*! 1460 * TODO: draw_y_data_label_lines not implemented. 1461 * \param dydl bool 1462 */ 1463 function SetDrawYDataLabelLines($dydl) 1464 { 1465 $this->draw_y_data_label_lines = $dydl; 1466 return TRUE; 1467 } 1468 1469 /*! 1470 * Sets the graph's title. 1471 * TODO: add parameter to choose title placement: left, right, centered= 1472 */ 1473 function SetTitle($which_title) 1474 { 1475 $this->title_txt = $which_title; 1476 1477 if ($which_title == '') { 1478 $this->title_height = 0; 1479 return TRUE; 1480 } 1481 1482 $str = split("\n", $which_title); 1483 $lines = count($str); 1484 $spacing = $this->line_spacing * ($lines - 1); 1485 1486 if ($this->use_ttf) { 1487 $size = $this->TTFBBoxSize($this->title_font['size'], 0, $this->title_font['font'], $which_title); 1488 $this->title_height = $size[1] * $lines; 1489 } else { 1490 $this->title_height = ($this->title_font['height'] + $spacing) * $lines; 1491 } 1492 return TRUE; 1493 } 1494 1495 /*! 1496 * Sets the X axis title and position. 1497 */ 1498 function SetXTitle($which_xtitle, $which_xpos = 'plotdown') 1499 { 1500 if ($which_xtitle == '') 1501 $which_xpos = 'none'; 1502 1503 $this->x_title_pos = $this->CheckOption($which_xpos, 'plotdown, plotup, both, none', __FUNCTION__); 1504 1505 $this->x_title_txt = $which_xtitle; 1506 1507 $str = split("\n", $which_xtitle); 1508 $lines = count($str); 1509 $spacing = $this->line_spacing * ($lines - 1); 1510 1511 if ($this->use_ttf) { 1512 $size = $this->TTFBBoxSize($this->x_title_font['size'], 0, $this->x_title_font['font'], $which_xtitle); 1513 $this->x_title_height = $size[1] * $lines; 1514 } else { 1515 $this->x_title_height = ($this->x_title_font['height'] + $spacing) * $lines; 1516 } 1517 1518 return TRUE; 1519 } 1520 1521 1522 /*! 1523 * Sets the Y axis title and position. 1524 */ 1525 function SetYTitle($which_ytitle, $which_ypos = 'plotleft') 1526 { 1527 if ($which_ytitle == '') 1528 $which_ypos = 'none'; 1529 1530 $this->y_title_pos = $this->CheckOption($which_ypos, 'plotleft, plotright, both, none', __FUNCTION__); 1531 1532 $this->y_title_txt = $which_ytitle; 1533 1534 $str = split("\n", $which_ytitle); 1535 $lines = count($str); 1536 $spacing = $this->line_spacing * ($lines - 1); 1537 1538 if ($this->use_ttf) { 1539 $size = $this->TTFBBoxSize($this->y_title_font['size'], 90, $this->y_title_font['font'], 1540 $which_ytitle); 1541 $this->y_title_width = $size[0] * $lines; 1542 } else { 1543 $this->y_title_width = ($this->y_title_font['height'] + $spacing) * $lines; 1544 } 1545 1546 return TRUE; 1547 } 1548 1549 /*! 1550 * Sets the size of the drop shadow for bar and pie charts. 1551 * \param which_s int Size in pixels. 1552 */ 1553 function SetShading($which_s) 1554 { 1555 $this->shading = (int)$which_s; 1556 return TRUE; 1557 } 1558 1559 function SetPlotType($which_pt) 1560 { 1561 $this->plot_type = $this->CheckOption($which_pt, 1562 'bars, stackedbars, lines, linepoints, area, points, pie, thinbarline, squared', 1563 __FUNCTION__); 1564 } 1565 1566 /*! 1567 * Sets the position of Y axis. 1568 * \param pos int Position in world coordinates. 1569 */ 1570 function SetYAxisPosition($pos) 1571 { 1572 $this->y_axis_position = (int)$pos; 1573 if (isset($this->scale_is_set)) { 1574 $this->CalcTranslation(); 1575 } 1576 return TRUE; 1577 } 1578 1579 /*! 1580 * Sets the position of X axis. 1581 * \param pos int Position in world coordinates. 1582 */ 1583 function SetXAxisPosition($pos) 1584 { 1585 $this->x_axis_position = (int)$pos; 1586 if (isset($this->scale_is_set)) { 1587 $this->CalcTranslation(); 1588 } 1589 return TRUE; 1590 } 1591 1592 1593 function SetXScaleType($which_xst) 1594 { 1595 $this->xscale_type = $this->CheckOption($which_xst, 'linear, log', __FUNCTION__); 1596 return TRUE; 1597 } 1598 1599 function SetYScaleType($which_yst) 1600 { 1601 $this->yscale_type = $this->CheckOption($which_yst, 'linear, log', __FUNCTION__); 1602 return TRUE; 1603 } 1604 1605 function SetPrecisionX($which_prec) 1606 { 1607 $this->x_precision = $which_prec; 1608 $this->SetXLabelType('data'); 1609 return TRUE; 1610 } 1611 1612 function SetPrecisionY($which_prec) 1613 { 1614 $this->y_precision = $which_prec; 1615 $this->SetYLabelType('data'); 1616 return TRUE; 1617 } 1618 1619 function SetErrorBarLineWidth($which_seblw) 1620 { 1621 $this->error_bar_line_width = $which_seblw; 1622 return TRUE; 1623 } 1624 1625 function SetLabelScalePosition($which_blp) 1626 { 1627 //0 to 1 1628 $this->label_scale_position = $which_blp; 1629 return TRUE; 1630 } 1631 1632 function SetErrorBarSize($which_ebs) 1633 { 1634 //in pixels 1635 $this->error_bar_size = $which_ebs; 1636 return TRUE; 1637 } 1638 1639 /*! 1640 * Can be one of: 'tee', 'line' 1641 */ 1642 function SetErrorBarShape($which_ebs) 1643 { 1644 $this->error_bar_shape = $this->CheckOption($which_ebs, 'tee, line', __FUNCTION__); 1645 } 1646 1647 /*! 1648 * Sets point shape for each data set via an array. 1649 * Shape can be one of: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot', 1650 * 'diamond', 'triangle', 'trianglemid', or 'none'. 1651 */ 1652 function SetPointShapes($which_pt) 1653 { 1654 if (is_null($which_pt)) { 1655 // Do nothing, use default value. 1656 } else if (is_array($which_pt)) { 1657 // Did we get an array with point shapes? 1658 $this->point_shapes = $which_pt; 1659 } else { 1660 // Single value into array 1661 $this->point_shapes = array($which_pt); 1662 } 1663 1664 foreach ($this->point_shapes as $shape) 1665 { 1666 // TODO, better check, per element rectification 1667 $this->CheckOption($shape, 1668 'halfline, line, plus, cross, rect, circle, dot, diamond, triangle, trianglemid, none', 1669 __FUNCTION__); 1670 } 1671 1672 // Make both point_shapes and point_sizes same size. 1673 $ps = count($this->point_sizes); 1674 $pt = count($this->point_shapes); 1675 1676 if ($ps < $pt) { 1677 array_pad_array($this->point_sizes, $pt); 1678 } else if ($pt > $ps) { 1679 array_pad_array($this->point_shapes, $ps); 1680 } 1681 return TRUE; 1682 } 1683 1684 /*! 1685 * Sets the point size for point plots. 1686 * \param ps int Size in pixels. 1687 * \note Test this more extensively 1688 */ 1689 function SetPointSizes($which_ps) 1690 { 1691 if (is_null($which_ps)) { 1692 // Do nothing, use default value. 1693 } else if (is_array($which_ps)) { 1694 // Did we get an array with point sizes? 1695 $this->point_sizes = $which_ps; 1696 } else { 1697 // Single value into array 1698 $this->point_sizes = array($which_ps); 1699 } 1700 1701 // Make both point_shapes and point_sizes same size. 1702 $ps = count($this->point_sizes); 1703 $pt = count($this->point_shapes); 1704 1705 if ($ps < $pt) { 1706 array_pad_array($this->point_sizes, $pt); 1707 } else if ($pt > $ps) { 1708 array_pad_array($this->point_shapes, $ps); 1709 } 1710 1711 // Fix odd point sizes for point shapes which need it 1712 for ($i = 0; $i < $pt; $i++) { 1713 if ($this->point_shapes[$i] == 'diamond' or $this->point_shapes[$i] == 'triangle') { 1714 if ($this->point_sizes[$i] % 2 != 0) { 1715 $this->point_sizes[$i]++; 1716 } 1717 } 1718 } 1719 return TRUE; 1720 } 1721 1722 1723 /*! 1724 * Tells not to draw lines for missing Y data. Only works with 'lines' and 'squared' plots. 1725 * \param bl bool 1726 */ 1727 function SetDrawBrokenLines($bl) 1728 { 1729 $this->draw_broken_lines = (bool)$bl; 1730 } 1731 1732 1733 /*! 1734 * text-data: ('label', y1, y2, y3, ...) 1735 * text-data-single: ('label', data), for some pie charts. 1736 * data-data: ('label', x, y1, y2, y3, ...) 1737 * data-data-error: ('label', x1, y1, e1+, e2-, y2, e2+, e2-, y3, e3+, e3-, ...) 1738 */ 1739 function SetDataType($which_dt) 1740 { 1741 //The next four lines are for past compatibility. 1742 if ($which_dt == 'text-linear') { $which_dt = 'text-data'; }; 1743 if ($which_dt == 'linear-linear') { $which_dt = 'data-data'; }; 1744 if ($which_dt == 'linear-linear-error') { $which_dt = 'data-data-error'; }; 1745 if ($which_dt == 'text-data-pie') { $which_dt = 'text-data-single'; } 1746 1747 1748 $this->data_type = $this->CheckOption($which_dt, 'text-data, text-data-single, '. 1749 'data-data, data-data-error', __FUNCTION__); 1750 return TRUE; 1751 } 1752 1753 /*! 1754 * Copy the array passed as data values. We convert to numerical indexes, for its 1755 * use for (or while) loops, which sometimes are faster. Performance improvements 1756 * vary from 28% in DrawLines() to 49% in DrawArea() for plot drawing functions. 1757 */ 1758 function SetDataValues(&$which_dv) 1759 { 1760 unset ($this->data_limits_done); // Reset this for every new data_set 1761 $this->num_data_rows = count($which_dv); 1762 $this->total_records = 0; // Perform some useful calculations. 1763 $this->records_per_group = 1; 1764 for ($i = 0, $recs = 0; $i < $this->num_data_rows; $i++) { 1765 // Copy 1766 $this->data[$i] = array_values($which_dv[$i]); // convert to numerical indices. 1767 1768 // Compute some values 1769 $recs = count($this->data[$i]); 1770 $this->total_records += $recs; 1771 1772 if ($recs > $this->records_per_group) 1773 $this->records_per_group = $recs; 1774 1775 $this->num_recs[$i] = $recs; 1776 } 1777 } 1778 1779 /*! 1780 * Pad styles arrays for later use by plot drawing functions: 1781 * This removes the need for $max_data_colors, etc. and $color_index = $color_index % $max_data_colors 1782 * in DrawBars(), DrawLines(), etc. 1783 */ 1784 function PadArrays() 1785 { 1786 array_pad_array($this->line_widths, $this->records_per_group); 1787 array_pad_array($this->line_styles, $this->records_per_group); 1788 1789 array_pad_array($this->data_colors, $this->records_per_group); 1790 array_pad_array($this->data_border_colors, $this->records_per_group); 1791 array_pad_array($this->error_bar_colors, $this->records_per_group); 1792 1793 $this->SetDataColors(); 1794 $this->SetDataBorderColors(); 1795 $this->SetErrorBarColors(); 1796 1797 return TRUE; 1798 } 1799 1800 1801////////////////////////////////////////////////////////// 1802/////////// DATA ANALYSIS, SCALING AND TRANSLATION 1803////////////////////////////////////////////////////////// 1804 1805 /*! 1806 * Analizes data and sets up internal maxima and minima 1807 * Needed by: CalcMargins(), ... 1808 * Text-Data is different than data-data graphs. For them what 1809 * we have, instead of X values, is # of records equally spaced on data. 1810 * text-data is passed in as $data[] = (title, y1, y2, y3, y4, ...) 1811 * data-data is passed in as $data[] = (title, x, y1, y2, y3, y4, ...) 1812 */ 1813 function FindDataLimits() 1814 { 1815 // Set some default min and max values before running through the data 1816 switch ($this->data_type) { 1817 case 'text-data': 1818 case 'text-data-single': 1819 $minx = 0; 1820 $maxx = $this->num_data_rows - 1 ; 1821 $miny = $this->data[0][1]; 1822 $maxy = $miny; 1823 break; 1824 default: //Everything else: data-data, etc, take first value 1825 $minx = $this->data[0][1]; 1826 $maxx = $minx; 1827 $miny = $this->data[0][2]; 1828 $maxy = $miny; 1829 break; 1830 } 1831 1832 $mine = 0; // Maximum value for the -error bar (assume error bars always > 0) 1833 $maxe = 0; // Maximum value for the +error bar (assume error bars always > 0) 1834 $maxt = 0; // Maximum number of characters in text labels 1835 1836 if ($this->plot_type == 'stackedbars') { 1837 $maxmaxy = $minminy = 0; 1838 } else { 1839 $minminy = $miny; 1840 $maxmaxy = $maxy; 1841 } 1842 1843 // Process each row of data 1844 for ($i=0; $i < $this->num_data_rows; $i++) { 1845 $j=0; 1846 // Extract maximum text label length 1847 $val = @ strlen($this->data[$i][$j++]); 1848 $maxt = ($val > $maxt) ? $val : $maxt; 1849 1850 switch ($this->data_type) { 1851 case 'text-data': // Data is passed in as (title, y1, y2, y3, ...) 1852 case 'text-data-single': // This one is for some pie charts 1853 // $numrecs = @ count($this->data[$i]); 1854 $maxy = (double)$this->data[$i][$j++]; 1855 if ($this->plot_type == 'stackedbars') { 1856 $miny = 0; 1857 } else { 1858 $miny = $maxy; 1859 } 1860 for (; $j < $this->num_recs[$i]; $j++) { 1861 $val = (double)$this->data[$i][$j]; 1862 if ($this->plot_type == 'stackedbars') { 1863 $maxy += abs($val); // only positive values for the moment 1864 } else { 1865 $maxy = ($val > $maxy) ? $val : $maxy; 1866 $miny = ($val < $miny) ? $val : $miny; 1867 } 1868 } 1869 break; 1870 case 'data-data': // Data is passed in as (title, x, y, y2, y3, ...) 1871 // X value: 1872 $val = (double)$this->data[$i][$j++]; 1873 $maxx = ($val > $maxx) ? $val : $maxx; 1874 $minx = ($val < $minx) ? $val : $minx; 1875 1876 $miny = $maxy = (double)$this->data[$i][$j++]; 1877 // $numrecs = @ count($this->data[$i]); 1878 for (; $j < $this->num_recs[$i]; $j++) { 1879 $val = (double)$this->data[$i][$j]; 1880 $maxy = ($val > $maxy) ? $val : $maxy; 1881 $miny = ($val < $miny) ? $val : $miny; 1882 } 1883 break; 1884 case 'data-data-error': // Data is passed in as (title, x, y, err+, err-, y2, err2+, err2-,...) 1885 // X value: 1886 $val = (double)$this->data[$i][$j++]; 1887 $maxx = ($val > $maxx) ? $val : $maxx; 1888 $minx = ($val < $minx) ? $val : $minx; 1889 1890 $miny = $maxy = (double)$this->data[$i][$j]; 1891 // $numrecs = @ count($this->data[$i]); 1892 for (; $j < $this->num_recs[$i];) { 1893 // Y value: 1894 $val = (double)$this->data[$i][$j++]; 1895 $maxy = ($val > $maxy) ? $val : $maxy; 1896 $miny = ($val < $miny) ? $val : $miny; 1897 // Error +: 1898 $val = (double)$this->data[$i][$j++]; 1899 $maxe = ($val > $maxe) ? $val : $maxe; 1900 // Error -: 1901 $val = (double)$this->data[$i][$j++]; 1902 $mine = ($val > $mine) ? $val : $mine; 1903 } 1904 $maxy = $maxy + $maxe; 1905 $miny = $miny - $mine; // assume error bars are always > 0 1906 break; 1907 default: 1908 $this->PrintError("FindDataLimits(): Unknown data type '$data_type'."); 1909 break; 1910 } 1911 $this->data[$i][MINY] = $miny; // This row's min Y, for DrawXDataLine() 1912 $this->data[$i][MAXY] = $maxy; // This row's max Y, for DrawXDataLine() 1913 1914 $minminy = ($miny < $minminy) ? $miny : $minminy; // global min 1915 $maxmaxy = ($maxy > $maxmaxy) ? $maxy : $maxmaxy; // global max 1916 } 1917 1918 $this->min_x = $minx; 1919 $this->max_x = $maxx; 1920 $this->min_y = $minminy; 1921 $this->max_y = $maxmaxy; 1922 $this->max_t = $maxt; 1923 1924 $this->data_limits_done = TRUE; 1925 1926 return TRUE; 1927 } 1928 1929 1930 /*! 1931 * Calculates image margins on the fly from title positions and sizes, 1932 * and tick labels positions and sizes. 1933 * 1934 * FIXME: fix x_data_label_pos behaviour. Now we are leaving room for it AND x_tick_label_pos 1935 * maybe it shouldn't be so... 1936 * 1937 * TODO: add x_tick_label_width and y_tick_label_height and use them to calculate 1938 * max_x_labels and max_y_labels, to be used by drawing functions to avoid overlapping. 1939 */ 1940 function CalcMargins() 1941 { 1942 // Temporary variables for label size calculation 1943 $xlab = $this->FormatLabel('x', $this->max_x); 1944 $ylab = $this->FormatLabel('y', $this->max_y); 1945 1946 // dirty fix: 1947 // max_t is the maximum data label length (from column 0 of each data row). 1948 if ($this->max_t > strlen ($xlab)) 1949 $xlab = sprintf ("%{$this->max_t}s","_"); 1950 1951 //////// Calculate maximum X/Y axis label height and width: 1952 1953 // TTFonts: 1954 if ($this->use_ttf) { 1955 // Maximum X axis label height 1956 $size = $this->TTFBBoxSize($this->x_label_font['size'], $this->x_label_angle, 1957 $this->x_label_font['font'], $xlab); 1958 $this->x_tick_label_height = $size[1]; 1959 1960 // Maximum Y axis label width 1961 $size = $this->TTFBBoxSize($this->y_label_font['size'], $this->y_label_angle, 1962 $this->y_label_font['font'], $ylab); 1963 $this->y_tick_label_width = $size[0]; 1964 } 1965 // Fixed fonts: 1966 else { 1967 // Maximum X axis label height 1968 if ($this->x_label_angle == 90) 1969 $this->x_tick_label_height = strlen($xlab) * $this->x_label_font['width']; 1970 else 1971 $this->x_tick_label_height = $this->x_label_font['height']; 1972 1973 // Maximum Y axis label width 1974 $this->y_tick_label_width = strlen($ylab) * $this->y_label_font['width']; 1975 } 1976 1977 1978 ///////// Calculate margins: 1979 1980 // Upper title, ticks and tick labels, and data labels: 1981 $this->y_top_margin = $this->title_height + $this->safe_margin * 2; 1982 1983 if ($this->x_title_pos == 'plotup' || $this->x_title_pos == 'both') 1984 $this->y_top_margin += $this->x_title_height + $this->safe_margin; 1985 1986 if ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both') 1987 $this->y_top_margin += $this->x_tick_label_height; 1988 1989 if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both') 1990 $this->y_top_margin += $this->x_tick_length * 2; 1991 1992 if ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both') 1993 $this->y_top_margin += $this->x_tick_label_height; 1994 1995 // Lower title, ticks and tick labels, and data labels: 1996 $this->y_bot_margin = $this->safe_margin * 2; 1997 1998 if ($this->x_title_pos == 'plotdown' || $this->x_title_pos == 'both') 1999 $this->y_bot_margin += $this->x_title_height; 2000 2001 if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both') 2002 $this->y_bot_margin += $this->x_tick_length * 2; 2003 2004 if ($this->x_tick_pos == 'xaxis' && ($this->x_axis_position == '' || $this->x_axis_position == 0)) 2005 $this->y_bot_margin += $this->x_tick_length * 2; 2006 2007 if ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both') 2008 $this->y_bot_margin += $this->x_tick_label_height; 2009 2010 if ($this->x_tick_label_pos == 'xaxis' && ($this->x_axis_position == '' || $this->x_axis_position == 0)) 2011 $this->y_bot_margin += $this->x_tick_label_height; 2012 2013 if ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both') 2014 $this->y_bot_margin += $this->x_tick_label_height; 2015 2016 // Left title, ticks and tick labels: 2017 $this->x_left_margin = $this->safe_margin * 2; 2018 2019 if ($this->y_title_pos == 'plotleft' || $this->y_title_pos == 'both') 2020 $this->x_left_margin += $this->y_title_width + $this->safe_margin; 2021 2022 if ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both') 2023 $this->x_left_margin += $this->y_tick_label_width; 2024 2025 if ($this->y_tick_pos == 'plotleft' || $this->y_tick_pos == 'both') 2026 $this->x_left_margin += $this->y_tick_length * 2 ; 2027 2028 // Right title, ticks and tick labels: 2029 $this->x_right_margin = $this->safe_margin * 2; 2030 2031 if ($this->y_title_pos == 'plotright' || $this->y_title_pos == 'both') 2032 $this->x_right_margin += $this->y_title_width + $this->safe_margin; 2033 2034 if ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both') 2035 $this->x_right_margin += $this->y_tick_label_width; 2036 2037 if ($this->y_tick_pos == 'plotright' || $this->y_tick_pos == 'both') 2038 $this->x_right_margin += $this->y_tick_length * 2; 2039 2040 2041 $this->x_tot_margin = $this->x_left_margin + $this->x_right_margin; 2042 $this->y_tot_margin = $this->y_top_margin + $this->y_bot_margin; 2043 2044 return; 2045 } 2046 2047 2048 /*! 2049 * Set the margins in pixels (left, right, top, bottom) 2050 */ 2051 function SetMarginsPixels($which_lm, $which_rm, $which_tm, $which_bm) 2052 { 2053 2054 $this->x_left_margin = $which_lm; 2055 $this->x_right_margin = $which_rm; 2056 $this->x_tot_margin = $which_lm + $which_rm; 2057 2058 $this->y_top_margin = $which_tm; 2059 $this->y_bot_margin = $which_bm; 2060 $this->y_tot_margin = $which_tm + $which_bm; 2061 2062 $this->SetPlotAreaPixels(); 2063 2064 return; 2065 } 2066 2067 2068 /*! 2069 * Sets the limits for the plot area. If no arguments are supplied, uses 2070 * values calculated from CalcMargins(); 2071 * Like in GD, (0,0) is upper left 2072 * 2073 * This resets the scale if SetPlotAreaWorld() was already called 2074 */ 2075 function SetPlotAreaPixels($x1=NULL, $y1=NULL, $x2=NULL, $y2=NULL) 2076 { 2077 if ($x2 && $y2) { 2078 $this->plot_area = array($x1, $y1, $x2, $y2); 2079 } else { 2080 if (! isset($this->x_tot_margin)) 2081 $this->CalcMargins(); 2082 2083 $this->plot_area = array($this->x_left_margin, $this->y_top_margin, 2084 $this->image_width - $this->x_right_margin, 2085 $this->image_height - $this->y_bot_margin); 2086 } 2087 $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0]; 2088 $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1]; 2089 2090 // Reset the scale with the new plot area. 2091 if (isset($this->plot_max_x)) 2092 $this->CalcTranslation(); 2093 2094 return TRUE; 2095 2096 } 2097 2098 2099 /*! 2100 * Sets minimum and maximum x and y values in the plot using FindDataLimits() 2101 * or from the supplied parameters, if any. 2102 * 2103 * This resets the scale if SetPlotAreaPixels() was already called 2104 */ 2105 function SetPlotAreaWorld($xmin=NULL, $ymin=NULL, $xmax=NULL, $ymax=NULL) 2106 { 2107 if (! isset($this->data_limits_done)) { // For automatic setting of data we need data limits 2108 $this->FindDataLimits() ; 2109 } 2110 2111 if ($xmin === NULL || $xmin === '') { 2112 if ($this->data_type == 'text-data') // Valid for data without X values only. 2113 $xmin = 0; 2114 else 2115 $xmin = $this->min_x; 2116 } 2117 if ($xmax === NULL || $xmax === '') { 2118 if ($this->data_type == 'text-data') // Valid for data without X values only. 2119 $xmax = $this->max_x + 1; 2120 else 2121 $xmax = $this->max_x; 2122 } 2123 2124 // Leave room above and below the highest and lowest data points. 2125 2126 if ($ymin === NULL || $ymin === '') { 2127 $ymin = floor($this->min_y - abs($this->min_y) * 0.1); 2128 } 2129 if ($ymax === NULL || $ymax === '') { 2130 $ymax = ceil($this->max_y + abs($this->max_y) * 0.1); 2131 } 2132 2133 // Error checking 2134 2135 if ($ymin == $ymax) // Minimum height 2136 $ymax += 1; 2137 2138 if ($this->yscale_type == 'log') { 2139 if ($ymin <= 0) { 2140 $ymin = 1; 2141 } 2142 if ($ymax <= 0) { 2143 $this->PrintError('SetPlotAreaWorld(): Log plots need data greater than 0'); 2144 return FALSE; 2145 } 2146 } 2147 2148 if ($ymax <= $ymin) { 2149 $this->DrawError('SetPlotAreaWorld(): Error in data - max not greater than min'); 2150 return FALSE; 2151 } 2152 2153 2154 // Reset (if it was already set) the scale with the new maxs and mins 2155 2156 $this->plot_min_x = $xmin; 2157 $this->plot_max_x = $xmax; 2158 $this->plot_min_y = $ymin; 2159 $this->plot_max_y = $ymax; 2160 2161 if (isset($this->plot_area_width)) { 2162 $this->CalcTranslation(); 2163 } 2164 2165 return TRUE; 2166 } //function SetPlotAreaWorld 2167 2168 2169 /*! 2170 * For bar plots, which have equally spaced x variables. 2171 */ 2172 function CalcBarWidths() 2173 { 2174 // group_width is the width of a group, including padding 2175 $group_width = $this->plot_area_width / $this->num_data_rows; 2176 2177 // Actual number of bar spaces in the group. This includes the drawn bars, and 2178 // 'bar_extra_space'-worth of extra bars. 2179 // Note that 'records_per_group' includes the label, so subtract one to get 2180 // the number of points in the group. 'stackedbars' have 1 bar space per group. 2181 if ($this->plot_type == 'stackedbars') { 2182 $num_spots = 1 + $this->bar_extra_space; 2183 } else { 2184 $num_spots = $this->records_per_group - 1 + $this->bar_extra_space; 2185 } 2186 2187 // record_bar_width is the width of each bar's allocated area. 2188 // If bar_width_adjust=1 this is the width of the bar, otherwise 2189 // the bar is centered inside record_bar_width. 2190 // The equation is: 2191 // group_frac_width * group_width = record_bar_width * num_spots 2192 $this->record_bar_width = $this->group_frac_width * $group_width / $num_spots; 2193 2194 // Note that the extra space due to group_frac_width and bar_extra_space will be 2195 // evenly divided on each side of the group: the drawn bars are centered in the group. 2196 2197 // Within each bar's allocated space, if bar_width_adjust=1 the bar fills the 2198 // space, otherwise it is centered. 2199 // This is the actual drawn bar width: 2200 $this->actual_bar_width = $this->record_bar_width * $this->bar_width_adjust; 2201 // This is the gap on each side of the bar (0 if bar_width_adjust=1): 2202 $this->bar_adjust_gap = ($this->record_bar_width - $this->actual_bar_width) / 2; 2203 2204 return TRUE; 2205 } 2206 2207 /*! 2208 * Calculates scaling stuff... 2209 */ 2210 function CalcTranslation() 2211 { 2212 if ($this->plot_max_x - $this->plot_min_x == 0) { // Check for div by 0 2213 $this->xscale = 0; 2214 } else { 2215 if ($this->xscale_type == 'log') { 2216 $this->xscale = ($this->plot_area_width)/(log10($this->plot_max_x) - log10($this->plot_min_x)); 2217 } else { 2218 $this->xscale = ($this->plot_area_width)/($this->plot_max_x - $this->plot_min_x); 2219 } 2220 } 2221 2222 if ($this->plot_max_y - $this->plot_min_y == 0) { // Check for div by 0 2223 $this->yscale = 0; 2224 } else { 2225 if ($this->yscale_type == 'log') { 2226 $this->yscale = ($this->plot_area_height)/(log10($this->plot_max_y) - log10($this->plot_min_y)); 2227 } else { 2228 $this->yscale = ($this->plot_area_height)/($this->plot_max_y - $this->plot_min_y); 2229 } 2230 } 2231 // GD defines x = 0 at left and y = 0 at TOP so -/+ respectively 2232 if ($this->xscale_type == 'log') { 2233 $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * log10($this->plot_min_x) ); 2234 } else { 2235 $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * $this->plot_min_x); 2236 } 2237 if ($this->yscale_type == 'log') { 2238 $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * log10($this->plot_min_y)); 2239 } else { 2240 $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * $this->plot_min_y); 2241 } 2242 2243 $this->scale_is_set = TRUE; 2244 2245 /************** FIXME?? *************/ 2246 // There should be a better place for this. 2247 2248 // User provided y axis position? 2249 if ($this->y_axis_position !== '') { 2250 // Make sure we draw our axis inside the plot 2251 $this->y_axis_position = ($this->y_axis_position < $this->plot_min_x) 2252 ? $this->plot_min_x : $this->y_axis_position; 2253 $this->y_axis_position = ($this->y_axis_position > $this->plot_max_x) 2254 ? $this->plot_max_x : $this->y_axis_position; 2255 $this->y_axis_x_pixels = $this->xtr($this->y_axis_position); 2256 } else { 2257 // Default to left axis 2258 $this->y_axis_x_pixels = $this->xtr($this->plot_min_x); 2259 } 2260 // User provided x axis position? 2261 if ($this->x_axis_position !== '') { 2262 // Make sure we draw our axis inside the plot 2263 $this->x_axis_position = ($this->x_axis_position < $this->plot_min_y) 2264 ? $this->plot_min_y : $this->x_axis_position; 2265 $this->x_axis_position = ($this->x_axis_position > $this->plot_max_y) 2266 ? $this->plot_max_y : $this->x_axis_position; 2267 } elseif ($this->yscale_type == 'log') { 2268 $this->x_axis_position = 1; 2269 } else { 2270 // Default to axis at 0 or plot_min_y (should be 0 anyway, from SetPlotAreaWorld()) 2271 // Note x_axis_position must be set because DrawBars and others expect a number. 2272 if ($this->plot_min_y <= 0 && 0 <= $this->plot_max_y) 2273 $this->x_axis_position = 0; 2274 else 2275 $this->x_axis_position = $this->plot_min_y; 2276 } 2277 $this->x_axis_y_pixels = $this->ytr($this->x_axis_position); 2278 2279 } // function CalcTranslation() 2280 2281 2282 /*! 2283 * Translate X world coordinate into pixel coordinate 2284 * Needs values calculated by _CalcTranslation() 2285 */ 2286 function xtr($x_world) 2287 { 2288 //$x_pixels = $this->x_left_margin + ($this->image_width - $this->x_tot_margin)* 2289 // (($x_world - $this->plot_min_x) / ($this->plot_max_x - $this->plot_min_x)) ; 2290 //which with a little bit of math reduces to ... 2291 if ($this->xscale_type == 'log') { 2292 $x_pixels = $this->plot_origin_x + log10($x_world) * $this->xscale ; 2293 } else { 2294 $x_pixels = $this->plot_origin_x + $x_world * $this->xscale ; 2295 } 2296 return round($x_pixels); 2297 } 2298 2299 2300 /*! 2301 * Translate Y world coordinate into pixel coordinate. 2302 * Needs values calculated by _CalcTranslation() 2303 */ 2304 function ytr($y_world) 2305 { 2306 if ($this->yscale_type == 'log') { 2307 //minus because GD defines y = 0 at top. doh! 2308 $y_pixels = $this->plot_origin_y - log10($y_world) * $this->yscale ; 2309 } else { 2310 $y_pixels = $this->plot_origin_y - $y_world * $this->yscale ; 2311 } 2312 return round($y_pixels); 2313 } 2314 2315 /*! 2316 * Formats a tick or data label. 2317 * 2318 * \note Time formatting suggested by Marlin Viss 2319 */ 2320 function FormatLabel($which_pos, $which_lab) 2321 { 2322 switch ($which_pos) { 2323 case 'x': 2324 case 'plotx': 2325 switch ($this->x_label_type) { 2326 case 'title': 2327 $lab = @ $this->data[$which_lab][0]; 2328 break; 2329 case 'data': 2330 $lab = number_format($which_lab, $this->x_precision, '.', ',').$this->data_units_text; 2331 break; 2332 case 'time': 2333 $lab = strftime($this->x_time_format, $which_lab); 2334 break; 2335 default: 2336 // Unchanged from whatever format it is passed in 2337 $lab = $which_lab; 2338 break; 2339 } 2340 break; 2341 case 'y': 2342 case 'ploty': 2343 switch ($this->y_label_type) { 2344 case 'data': 2345 $lab = number_format($which_lab, $this->y_precision, '.', ',').$this->data_units_text; 2346 break; 2347 case 'time': 2348 $lab = strftime($this->y_time_format, $which_lab); 2349 break; 2350 default: 2351 // Unchanged from whatever format it is passed in 2352 $lab = $which_lab; 2353 break; 2354 } 2355 break; 2356 default: 2357 $this->PrintError("FormatLabel(): Unknown label type $which_type"); 2358 return NULL; 2359 } 2360 2361 return $lab; 2362 } //function FormatLabel 2363 2364 2365 2366///////////////////////////////////////////// 2367/////////////// TICKS 2368///////////////////////////////////////////// 2369 2370 /*! 2371 * Use either this or SetNumXTicks() to set where to place x tick marks 2372 */ 2373 function SetXTickIncrement($which_ti=NULL) 2374 { 2375 if ($which_ti) { 2376 $this->x_tick_inc = $which_ti; //world coordinates 2377 } else { 2378 if (! isset($this->data_limits_done)) { 2379 $this->FindDataLimits(); //Get maxima and minima for scaling 2380 } 2381 $this->x_tick_inc = ($this->plot_max_x - $this->plot_min_x )/10; 2382 } 2383 $this->num_x_ticks = ''; //either use num_y_ticks or y_tick_inc, not both 2384 return TRUE; 2385 } 2386 2387 /*! 2388 * Use either this or SetNumYTicks() to set where to place y tick marks 2389 */ 2390 function SetYTickIncrement($which_ti=NULL) 2391 { 2392 if ($which_ti) { 2393 $this->y_tick_inc = $which_ti; //world coordinates 2394 } else { 2395 if (! isset($this->data_limits_done)) { 2396 $this->FindDataLimits(); //Get maxima and minima for scaling 2397 } 2398 if (! isset($this->plot_max_y)) 2399 $this->SetPlotAreaWorld(); 2400 2401 $this->y_tick_inc = ($this->plot_max_y - $this->plot_min_y )/10; 2402 } 2403 $this->num_y_ticks = ''; //either use num_y_ticks or y_tick_inc, not both 2404 return TRUE; 2405 } 2406 2407 2408 function SetNumXTicks($which_nt) 2409 { 2410 $this->num_x_ticks = $which_nt; 2411 $this->x_tick_inc = ''; //either use num_x_ticks or x_tick_inc, not both 2412 return TRUE; 2413 } 2414 2415 function SetNumYTicks($which_nt) 2416 { 2417 $this->num_y_ticks = $which_nt; 2418 $this->y_tick_inc = ''; //either use num_y_ticks or y_tick_inc, not both 2419 return TRUE; 2420 } 2421 2422 /*! 2423 * 2424 */ 2425 function SetYTickPos($which_tp) 2426 { 2427 $this->y_tick_pos = $this->CheckOption($which_tp, 'plotleft, plotright, both, yaxis, none', __FUNCTION__); 2428 return TRUE; 2429 } 2430 /*! 2431 * 2432 */ 2433 function SetXTickPos($which_tp) 2434 { 2435 $this->x_tick_pos = $this->CheckOption($which_tp, 'plotdown, plotup, both, xaxis, none', __FUNCTION__); 2436 return TRUE; 2437 } 2438 2439 /*! 2440 * \param skip bool 2441 */ 2442 function SetSkipTopTick($skip) 2443 { 2444 $this->skip_top_tick = (bool)$skip; 2445 return TRUE; 2446 } 2447 2448 /*! 2449 * \param skip bool 2450 */ 2451 function SetSkipBottomTick($skip) 2452 { 2453 $this->skip_bottom_tick = (bool)$skip; 2454 return TRUE; 2455 } 2456 2457 /*! 2458 * \param skip bool 2459 */ 2460 function SetSkipLeftTick($skip) 2461 { 2462 $this->skip_left_tick = (bool)$skip; 2463 return TRUE; 2464 } 2465 2466 /*! 2467 * \param skip bool 2468 */ 2469 function SetSkipRightTick($skip) 2470 { 2471 $this->skip_right_tick = (bool)$skip; 2472 return TRUE; 2473 } 2474 2475 function SetXTickLength($which_xln) 2476 { 2477 $this->x_tick_length = $which_xln; 2478 return TRUE; 2479 } 2480 2481 function SetYTickLength($which_yln) 2482 { 2483 $this->y_tick_length = $which_yln; 2484 return TRUE; 2485 } 2486 2487 function SetXTickCrossing($which_xc) 2488 { 2489 $this->x_tick_cross = $which_xc; 2490 return TRUE; 2491 } 2492 2493 function SetYTickCrossing($which_yc) 2494 { 2495 $this->y_tick_cross = $which_yc; 2496 return TRUE; 2497 } 2498 2499 2500///////////////////////////////////////////// 2501//////////////////// GENERIC DRAWING 2502///////////////////////////////////////////// 2503 2504 /*! 2505 * Fills the background. 2506 */ 2507 function DrawBackground() 2508 { 2509 // Don't draw this twice if drawing two plots on one image 2510 if (! $this->background_done) { 2511 if (isset($this->bgimg)) { // If bgimg is defined, use it 2512 $this->tile_img($this->bgimg, 0, 0, $this->image_width, $this->image_height, $this->bgmode); 2513 } else { // Else use solid color 2514 ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, 2515 $this->ndx_bg_color); 2516 } 2517 $this->background_done = TRUE; 2518 return TRUE; // Done 2519 } 2520 return FALSE; // Nothing done 2521 } 2522 2523 2524 /*! 2525 * Fills the plot area background. 2526 */ 2527 function DrawPlotAreaBackground() 2528 { 2529 if (isset($this->plotbgimg)) { 2530 $this->tile_img($this->plotbgimg, $this->plot_area[0], $this->plot_area[1], 2531 $this->plot_area_width, $this->plot_area_height, $this->plotbgmode); 2532 } 2533 else { 2534 if ($this->draw_plot_area_background) { 2535 ImageFilledRectangle($this->img, $this->plot_area[0], $this->plot_area[1], 2536 $this->plot_area[2], $this->plot_area[3], $this->ndx_plot_bg_color); 2537 } 2538 } 2539 2540 return TRUE; 2541 } 2542 2543 2544 /*! 2545 * Tiles an image at some given coordinates. 2546 * 2547 * \param $file string Filename of the picture to be used as tile. 2548 * \param $xorig int X coordinate of the plot where the tile is to begin. 2549 * \param $yorig int Y coordinate of the plot where the tile is to begin. 2550 * \param $width int Width of the area to be tiled. 2551 * \param $height int Height of the area to be tiled. 2552 * \param $mode string One of 'centeredtile', 'tile', 'scale'. 2553 */ 2554 function tile_img($file, $xorig, $yorig, $width, $height, $mode) 2555 { 2556 $size = getimagesize($file); 2557 $input_format = $size[2]; 2558 2559 switch($input_format) { 2560 case 1: 2561 $im = @ imagecreatefromGIF ($file); 2562 if (! $im) { 2563 $this->PrintError("tile_img:() Unable to open $file as a GIF."); 2564 return FALSE; 2565 } 2566 break; 2567 case 2: 2568 $im = @ imagecreatefromJPEG ($file); 2569 if (! $im) { 2570 $this->PrintError("tile_img(): Unable to open $file as a JPG."); 2571 return FALSE; 2572 } 2573 break; 2574 case 3: 2575 $im = @ imagecreatefromPNG ($file); 2576 if (! $im) { 2577 $this->PrintError("tile_img(): Unable to open $file as a PNG."); 2578 return FALSE; 2579 } 2580 break; 2581 default: 2582 $this->PrintError('tile_img(): Please select a gif, jpg, or png image.'); 2583 return FALSE; 2584 break; 2585 } 2586 2587 2588 if ($mode == 'scale') { 2589 imagecopyresized($this->img, $im, $xorig, $yorig, 0, 0, $width, $height, $size[0],$size[1]); 2590 return TRUE; 2591 } else if ($mode == 'centeredtile') { 2592 $x0 = - floor($size[0]/2); // Make the tile look better 2593 $y0 = - floor($size[1]/2); 2594 } else if ($mode = 'tile') { 2595 $x0 = 0; 2596 $y0 = 0; 2597 } 2598 2599 // Actually draw the tile 2600 2601 // But first on a temporal image. 2602 $tmp = ImageCreate($width, $height); 2603 if (! $tmp) 2604 $this->PrintError('tile_img(): Could not create image resource.'); 2605 2606 for ($x = $x0; $x < $width; $x += $size[0]) 2607 for ($y = $y0; $y < $height; $y += $size[1]) 2608 imagecopy($tmp, $im, $x, $y, 0, 0, $size[0], $size[1]); 2609 2610 // Copy the temporal image onto the final one. 2611 imagecopy($this->img, $tmp, $xorig, $yorig, 0,0, $width, $height); 2612 2613 // Free resources 2614 imagedestroy($tmp); 2615 imagedestroy($im); 2616 2617 return TRUE; 2618 } // function tile_img 2619 2620 2621 /*! 2622 * Draws a border around the final image. 2623 */ 2624 function DrawImageBorder() 2625 { 2626 switch ($this->image_border_type) { 2627 case 'raised': 2628 ImageLine($this->img, 0, 0, $this->image_width-1, 0, $this->ndx_i_border); 2629 ImageLine($this->img, 1, 1, $this->image_width-2, 1, $this->ndx_i_border); 2630 ImageLine($this->img, 0, 0, 0, $this->image_height-1, $this->ndx_i_border); 2631 ImageLine($this->img, 1, 1, 1, $this->image_height-2, $this->ndx_i_border); 2632 ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1, 2633 $this->image_height-1, $this->ndx_i_border_dark); 2634 ImageLine($this->img, 0, $this->image_height-1, $this->image_width-1, 2635 $this->image_height-1, $this->ndx_i_border_dark); 2636 ImageLine($this->img, $this->image_width-2, 1, $this->image_width-2, 2637 $this->image_height-2, $this->ndx_i_border_dark); 2638 ImageLine($this->img, 1, $this->image_height-2, $this->image_width-2, 2639 $this->image_height-2, $this->ndx_i_border_dark); 2640 break; 2641 case 'plain': 2642 ImageLine($this->img, 0, 0, $this->image_width-1, 0, $this->ndx_i_border_dark); 2643 ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1, 2644 $this->image_height-1, $this->ndx_i_border_dark); 2645 ImageLine($this->img, $this->image_width-1, $this->image_height-1, 0, $this->image_height-1, 2646 $this->ndx_i_border_dark); 2647 ImageLine($this->img, 0, 0, 0, $this->image_height-1, $this->ndx_i_border_dark); 2648 break; 2649 case 'none': 2650 break; 2651 default: 2652 $this->DrawError("DrawImageBorder(): unknown image_border_type: '$this->image_border_type'"); 2653 return FALSE; 2654 } 2655 return TRUE; 2656 } 2657 2658 2659 /*! 2660 * Adds the title to the graph. 2661 */ 2662 function DrawTitle() 2663 { 2664 // Center of the plot area 2665 //$xpos = ($this->plot_area[0] + $this->plot_area_width )/ 2; 2666 2667 // Center of the image: 2668 $xpos = $this->image_width / 2; 2669 2670 // Place it at almost at the top 2671 $ypos = $this->safe_margin; 2672 2673 $this->DrawText($this->title_font, $this->title_angle, $xpos, $ypos, 2674 $this->ndx_title_color, $this->title_txt, 'center', 'bottom'); 2675 2676 return TRUE; 2677 2678 } 2679 2680 2681 /*! 2682 * Draws the X-Axis Title 2683 */ 2684 function DrawXTitle() 2685 { 2686 if ($this->x_title_pos == 'none') 2687 return; 2688 2689 // Center of the plot 2690 $xpos = ($this->plot_area[2] + $this->plot_area[0]) / 2; 2691 2692 // Upper title 2693 if ($this->x_title_pos == 'plotup' || $this->x_title_pos == 'both') { 2694 $ypos = $this->safe_margin + $this->title_height + $this->safe_margin; 2695 $this->DrawText($this->x_title_font, $this->x_title_angle, 2696 $xpos, $ypos, $this->ndx_title_color, $this->x_title_txt, 'center'); 2697 } 2698 // Lower title 2699 if ($this->x_title_pos == 'plotdown' || $this->x_title_pos == 'both') { 2700 $ypos = $this->image_height - $this->x_title_height - $this->safe_margin; 2701 $this->DrawText($this->x_title_font, $this->x_title_angle, 2702 $xpos, $ypos, $this->ndx_title_color, $this->x_title_txt, 'center'); 2703 } 2704 return TRUE; 2705 } 2706 2707 /*! 2708 * Draws the Y-Axis Title 2709 */ 2710 function DrawYTitle() 2711 { 2712 if ($this->y_title_pos == 'none') 2713 return; 2714 2715 // Center the title vertically to the plot 2716 $ypos = ($this->plot_area[3] + $this->plot_area[1]) / 2; 2717 2718 if ($this->y_title_pos == 'plotleft' || $this->y_title_pos == 'both') { 2719 $xpos = $this->safe_margin; 2720 $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color, 2721 $this->y_title_txt, 'left', 'center'); 2722 } 2723 if ($this->y_title_pos == 'plotright' || $this->y_title_pos == 'both') { 2724 $xpos = $this->image_width - $this->safe_margin - $this->y_title_width - $this->safe_margin; 2725 $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color, 2726 $this->y_title_txt, 'left', 'center'); 2727 } 2728 2729 return TRUE; 2730 } 2731 2732 2733 /* 2734 * \note Horizontal grid lines overwrite horizontal axis with y=0, so call this first, then DrawXAxis() 2735 */ 2736 function DrawYAxis() 2737 { 2738 // Draw ticks, labels and grid, if any 2739 $this->DrawYTicks(); 2740 2741 // Draw Y axis at X = y_axis_x_pixels 2742 ImageLine($this->img, $this->y_axis_x_pixels, $this->plot_area[1], 2743 $this->y_axis_x_pixels, $this->plot_area[3], $this->ndx_grid_color); 2744 2745 return TRUE; 2746 } 2747 2748 /* 2749 * 2750 */ 2751 function DrawXAxis() 2752 { 2753 // Draw ticks, labels and grid 2754 $this->DrawXTicks(); 2755 2756 /* This tick and label tend to overlap with regular Y Axis labels, 2757 * as Mike Pullen pointed out. 2758 * 2759 //Draw Tick and Label for X axis 2760 if (! $this->skip_bottom_tick) { 2761 $ylab =$this->FormatLabel('y', $this->x_axis_position); 2762 $this->DrawYTick($ylab, $this->x_axis_y_pixels); 2763 } 2764 */ 2765 //Draw X Axis at Y = x_axis_y_pixels 2766 ImageLine($this->img, $this->plot_area[0]+1, $this->x_axis_y_pixels, 2767 $this->plot_area[2]-1, $this->x_axis_y_pixels, $this->ndx_grid_color); 2768 2769 return TRUE; 2770 } 2771 2772 /*! 2773 * Draw Just one Tick, called from DrawYTicks() and DrawXAxis() 2774 * TODO? Move this inside DrawYTicks() and Modify DrawXAxis() ? 2775 */ 2776 function DrawYTick($which_ylab, $which_ypix) 2777 { 2778 // Ticks on Y axis 2779 if ($this->y_tick_pos == 'yaxis') { 2780 ImageLine($this->img, $this->y_axis_x_pixels - $this->y_tick_length, $which_ypix, 2781 $this->y_axis_x_pixels + $this->y_tick_cross, $which_ypix, 2782 $this->ndx_tick_color); 2783 } 2784 2785 // Labels on Y axis 2786 if ($this->y_tick_label_pos == 'yaxis') { 2787 $this->DrawText($this->y_label_font, $this->y_label_angle, 2788 $this->y_axis_x_pixels - $this->y_tick_length * 1.5, $which_ypix, 2789 $this->ndx_text_color, $which_ylab, 'right', 'center'); 2790 } 2791 2792 // Ticks to the left of the Plot Area 2793 if (($this->y_tick_pos == 'plotleft') || ($this->y_tick_pos == 'both') ) { 2794 ImageLine($this->img, $this->plot_area[0] - $this->y_tick_length, 2795 $which_ypix, $this->plot_area[0] + $this->y_tick_cross, 2796 $which_ypix, $this->ndx_tick_color); 2797 } 2798 2799 // Ticks to the right of the Plot Area 2800 if (($this->y_tick_pos == 'plotright') || ($this->y_tick_pos == 'both') ) { 2801 ImageLine($this->img, ($this->plot_area[2] + $this->y_tick_length), 2802 $which_ypix, $this->plot_area[2] - $this->y_tick_cross, 2803 $which_ypix, $this->ndx_tick_color); 2804 } 2805 2806 // Labels to the left of the plot area 2807 if ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both') { 2808 $this->DrawText($this->y_label_font, $this->y_label_angle, 2809 $this->plot_area[0] - $this->y_tick_length * 1.5, $which_ypix, 2810 $this->ndx_text_color, $which_ylab, 'right', 'center'); 2811 } 2812 // Labels to the right of the plot area 2813 if ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both') { 2814 $this->DrawText($this->y_label_font, $this->y_label_angle, 2815 $this->plot_area[2] + $this->y_tick_length * 1.5, $which_ypix, 2816 $this->ndx_text_color, $which_ylab, 'left', 'center'); 2817 } 2818 } // Function DrawYTick() 2819 2820 2821 /*! 2822 * Draws Grid, Ticks and Tick Labels along Y-Axis 2823 * Ticks and ticklabels can be left of plot only, right of plot only, 2824 * both on the left and right of plot, or crossing a user defined Y-axis 2825 * TODO: marks at whole numbers (-10, 10, 20, 30 ...) no matter where the plot begins (-3, 4.7, etc.) 2826 */ 2827 function DrawYTicks() 2828 { 2829 // Sets the line style for IMG_COLOR_STYLED lines (grid) 2830 if ($this->dashed_grid) { 2831 $this->SetDashedStyle($this->ndx_light_grid_color); 2832 $style = IMG_COLOR_STYLED; 2833 } else { 2834 $style = $this->ndx_light_grid_color; 2835 } 2836 2837 // maxy is always > miny so delta_y is always positive 2838 if ($this->y_tick_inc) { 2839 $delta_y = $this->y_tick_inc; 2840 } elseif ($this->num_y_ticks) { 2841 $delta_y = ($this->plot_max_y - $this->plot_min_y) / $this->num_y_ticks; 2842 } else { 2843 $delta_y = ($this->plot_max_y - $this->plot_min_y) / 10 ; 2844 } 2845 2846 // NOTE: When working with floats, because of approximations when adding $delta_y, 2847 // $y_tmp never equals $y_end at the for loop, so one spurious line would get drawn where 2848 // not for the substraction to $y_end here. 2849 $y_tmp = (double)$this->plot_min_y; 2850 $y_end = (double)$this->plot_max_y - ($delta_y/2); 2851 2852 if ($this->skip_bottom_tick) 2853 $y_tmp += $delta_y; 2854 2855 if ($this->skip_top_tick) 2856 $y_end -= $delta_y; 2857 2858 for (;$y_tmp < $y_end; $y_tmp += $delta_y) { 2859 $ylab = $this->FormatLabel('y', $y_tmp); 2860 $y_pixels = $this->ytr($y_tmp); 2861 2862 // Horizontal grid line 2863 if ($this->draw_y_grid) { 2864 ImageLine($this->img, $this->plot_area[0]+1, $y_pixels, $this->plot_area[2]-1, $y_pixels, $style); 2865 } 2866 2867 // Draw ticks 2868 $this->DrawYTick($ylab, $y_pixels); 2869 } 2870 return TRUE; 2871 } // function DrawYTicks 2872 2873 2874 /*! 2875 * Draws Grid, Ticks and Tick Labels along X-Axis 2876 * Ticks and tick labels can be down of plot only, up of plot only, 2877 * both on up and down of plot, or crossing a user defined X-axis 2878 * 2879 * \note Original vertical code submitted by Marlin Viss 2880 */ 2881 function DrawXTicks() 2882 { 2883 // Sets the line style for IMG_COLOR_STYLED lines (grid) 2884 if ($this->dashed_grid) { 2885 $this->SetDashedStyle($this->ndx_light_grid_color); 2886 $style = IMG_COLOR_STYLED; 2887 } else { 2888 $style = $this->ndx_light_grid_color; 2889 } 2890 2891 // Calculate x increment between ticks 2892 if ($this->x_tick_inc) { 2893 $delta_x = $this->x_tick_inc; 2894 } elseif ($this->num_x_ticks) { 2895 $delta_x = ($this->plot_max_x - $this->plot_min_x) / $this->num_x_ticks; 2896 } else { 2897 $delta_x =($this->plot_max_x - $this->plot_min_x) / 10 ; 2898 } 2899 2900 // NOTE: When working with decimals, because of approximations when adding $delta_x, 2901 // $x_tmp never equals $x_end at the for loop, so one spurious line would get drawn where 2902 // not for the substraction to $x_end here. 2903 $x_tmp = (double)$this->plot_min_x; 2904 $x_end = (double)$this->plot_max_x - ($delta_x/2); 2905 2906 // Should the leftmost tick be drawn? 2907 if ($this->skip_left_tick) 2908 $x_tmp += $delta_x; 2909 2910 // And the rightmost? 2911 if (! $this->skip_right_tick) 2912 $x_end += $delta_x; 2913 2914 for (;$x_tmp < $x_end; $x_tmp += $delta_x) { 2915 $xlab = $this->FormatLabel('x', $x_tmp); 2916 $x_pixels = $this->xtr($x_tmp); 2917 2918 // Vertical grid lines 2919 if ($this->draw_x_grid) { 2920 ImageLine($this->img, $x_pixels, $this->plot_area[1], $x_pixels, $this->plot_area[3], $style); 2921 } 2922 2923 // Tick on X Axis 2924 if ($this->x_tick_pos == 'xaxis') { 2925 2926 ImageLine($this->img, $x_pixels, $this->x_axis_y_pixels - $this->x_tick_cross, 2927 $x_pixels, $this->x_axis_y_pixels + $this->x_tick_length, $this->ndx_tick_color); 2928 } 2929 2930 // Label on X axis 2931 if ($this->x_tick_label_pos == 'xaxis') { 2932 $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels, 2933 $this->x_axis_y_pixels + $this->x_tick_length*1.5, $this->ndx_text_color, 2934 $xlab, 'center', 'bottom'); 2935 } 2936 2937 // Top of the plot area tick 2938 if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both') { 2939 ImageLine($this->img, $x_pixels, $this->plot_area[1] - $this->x_tick_length, 2940 $x_pixels, $this->plot_area[1] + $this->x_tick_cross, $this->ndx_tick_color); 2941 } 2942 // Bottom of the plot area tick 2943 if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both') { 2944 ImageLine($this->img, $x_pixels, $this->plot_area[3] + $this->x_tick_length, 2945 $x_pixels, $this->plot_area[3] - $this->x_tick_cross, $this->ndx_tick_color); 2946 } 2947 2948 // Top of the plot area tick label 2949 if ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both') { 2950 $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels, 2951 $this->plot_area[1] - $this->x_tick_length*1.5, $this->ndx_text_color, 2952 $xlab, 'center', 'top'); 2953 } 2954 2955 // Bottom of the plot area tick label 2956 if ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both') { 2957 $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels, 2958 $this->plot_area[3] + $this->x_tick_length*1.5, $this->ndx_text_color, 2959 $xlab, 'center', 'bottom'); 2960 } 2961 } 2962 return; 2963 } // function DrawXTicks 2964 2965 2966 /*! 2967 * 2968 */ 2969 function DrawPlotBorder() 2970 { 2971 switch ($this->plot_border_type) { 2972 case 'left': // for past compatibility 2973 case 'plotleft': 2974 ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y), 2975 $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color); 2976 break; 2977 case 'right': 2978 case 'plotright': 2979 ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y), 2980 $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color); 2981 break; 2982 case 'both': 2983 case 'sides': 2984 ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y), 2985 $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color); 2986 ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y), 2987 $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color); 2988 break; 2989 case 'none': 2990 //Draw No Border 2991 break; 2992 case 'full': 2993 default: 2994 ImageRectangle($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y), 2995 $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color); 2996 break; 2997 } 2998 return TRUE; 2999 } 3000 3001 3002 /*! 3003 * Draws the data label associated with a point in the plot at specified x/y world position. 3004 * Calling of this function in DrawLines(), etc is decided after x_data_label_pos value. 3005 * what the setting is (for plots that need it, like DrawSquared()) 3006 */ 3007 function DrawDataLabel($which_font, $which_angle, $x_world, $y_world, $which_color, $which_text, 3008 $which_halign = 'center', $which_valign = 'top', $x_adjustment=0, $y_adjustment=0) 3009 { 3010 $data_label = $this->FormatLabel('y', $which_text); 3011 //since DrawDataLabel is going to be called alot - perhaps for speed it is better to 3012 //not use this if statement and just always assume which_font is x_label_font (ditto for color). 3013 if ( empty($which_font) ) 3014 $which_font = $this->x_label_font; 3015 3016 $which_angle = empty($which_angle)?'0':$this->x_label_angle; 3017 3018 if ( empty($which_color) ) 3019 $which_color = $this->ndx_title_color; 3020 3021 $x_pixels = $this->xtr($x_world) + $x_adjustment; 3022 $y_pixels = $this->ytr($y_world) + $y_adjustment; 3023 3024 $this->DrawText($which_font, $which_angle, $x_pixels, $y_pixels, 3025 $which_color, $which_text, $which_halign, $which_valign); 3026 3027 return TRUE; 3028 3029 } 3030 /*! 3031 * Draws the data label associated with a point in the plot. 3032 * This is different from x_labels drawn by DrawXTicks() and care 3033 * should be taken not to draw both, as they'd probably overlap. 3034 * Calling of this function in DrawLines(), etc is decided after x_data_label_pos value. 3035 * Leave the last parameter out, to avoid the drawing of vertical lines, no matter 3036 * what the setting is (for plots that need it, like DrawSquared()) 3037 */ 3038 function DrawXDataLabel($xlab, $xpos, $row=FALSE) 3039 { 3040 // FIXME!! not working... 3041 if (($this->_x_label_cnt++ % $this->x_label_inc) != 0) 3042 return; 3043 3044 $xlab = $this->FormatLabel('x', $xlab); 3045 3046 // Labels below the plot area 3047 if ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both') 3048 $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos, 3049 $this->plot_area[3] + $this->x_tick_length, 3050 $this->ndx_text_color, $xlab, 'center', 'bottom'); 3051 3052 // Labels above the plot area 3053 if ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both') 3054 $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos, 3055 $this->plot_area[1] - $this->x_tick_length , 3056 $this->ndx_text_color, $xlab, 'center', 'top'); 3057 3058 // $row=0 means this is the first row. $row=FALSE means don't do any rows. 3059 if ($row !== FALSE && $this->draw_x_data_label_lines) 3060 $this->DrawXDataLine($xpos, $row); 3061 } 3062 3063 /*! 3064 * Draws Vertical lines from data points up and down. 3065 * Which lines are drawn depends on the value of x_data_label_pos, 3066 * and whether this is at all done or not, on draw_x_data_label_lines 3067 * 3068 * \param xpos int position in pixels of the line. 3069 * \param row int index of the data row being drawn. 3070 */ 3071 function DrawXDataLine($xpos, $row) 3072 { 3073 // Sets the line style for IMG_COLOR_STYLED lines (grid) 3074 if($this->dashed_grid) { 3075 $this->SetDashedStyle($this->ndx_light_grid_color); 3076 $style = IMG_COLOR_STYLED; 3077 } else { 3078 $style = $this->ndx_light_grid_color; 3079 } 3080 3081 // Lines from the bottom up 3082 if ($this->x_data_label_pos == 'both') { 3083 ImageLine($this->img, $xpos, $this->plot_area[3], $xpos, $this->plot_area[1], $style); 3084 } 3085 // Lines coming from the bottom of the plot 3086 else if ($this->x_data_label_pos == 'plotdown') { 3087 // See FindDataLimits() to see why 'MAXY' index. 3088 $ypos = $this->ytr($this->data[$row][MAXY]); 3089 ImageLine($this->img, $xpos, $ypos, $xpos, $this->plot_area[3], $style); 3090 } 3091 // Lines coming from the top of the plot 3092 else if ($this->x_data_label_pos == 'plotup') { 3093 // See FindDataLimits() to see why 'MINY' index. 3094 $ypos = $this->ytr($this->data[$row][MINY]); 3095 ImageLine($this->img, $xpos, $this->plot_area[1], $xpos, $ypos, $style); 3096 } 3097 } 3098 3099/* 3100 function DrawPlotLabel($xlab, $xpos, $ypos) 3101 { 3102 $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos, $this 3103*/ 3104 3105 /*! 3106 * Draws the graph legend 3107 * 3108 * \note Base code submitted by Marlin Viss 3109 * FIXME: maximum label length should be calculated more accurately for TT fonts 3110 * Performing a BBox calculation for every legend element, for example. 3111 */ 3112 function DrawLegend($which_x1, $which_y1, $which_boxtype) 3113 { 3114 // Find maximum legend label length 3115 $max_len = 0; 3116 foreach ($this->legend as $leg) { 3117 $len = strlen($leg); 3118 $max_len = ($len > $max_len) ? $len : $max_len; 3119 } 3120 $max_len += 5; // Leave room for the boxes and margins 3121 3122 /////// Calculate legend labels sizes: FIXME - dirty hack - FIXME 3123 // TTF: 3124 if ($this->use_ttf) { 3125 $size = $this->TTFBBoxSize($this->legend_font['size'], 0, 3126 $this->legend_font['font'], '_'); 3127 $char_w = $size[0]; 3128 3129 $size = $this->TTFBBoxSize($this->legend_font['size'], 0, 3130 $this->legend_font['font'], '|'); 3131 $char_h = $size[1]; 3132 } 3133 // Fixed fonts: 3134 else { 3135 $char_w = $this->legend_font['width']; 3136 $char_h = $this->legend_font['height']; 3137 } 3138 3139 $v_margin = $char_h/2; // Between vertical borders and labels 3140 $dot_height = $char_h + $this->line_spacing; // Height of the small colored boxes 3141 $width = $char_w * $max_len; 3142 3143 //////// Calculate box size 3144 // upper Left 3145 if ( (! $which_x1) || (! $which_y1) ) { 3146 $box_start_x = $this->plot_area[2] - $width; 3147 $box_start_y = $this->plot_area[1] + 5; 3148 } else { 3149 $box_start_x = $which_x1; 3150 $box_start_y = $which_y1; 3151 } 3152 3153 // Lower right corner 3154 $box_end_y = $box_start_y + $dot_height*(count($this->legend)) + 2*$v_margin; 3155 $box_end_x = $box_start_x + $width - 5; 3156 3157 3158 // Draw outer box 3159 ImageFilledRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_bg_color); 3160 ImageRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_grid_color); 3161 3162 $color_index = 0; 3163 $max_color_index = count($this->ndx_data_colors) - 1; 3164 3165 $dot_left_x = $box_end_x - $char_w * 2; 3166 $dot_right_x = $box_end_x - $char_w; 3167 $y_pos = $box_start_y + $v_margin; 3168 3169 foreach ($this->legend as $leg) { 3170 // Text right aligned to the little box 3171 $this->DrawText($this->legend_font, 0, $dot_left_x - $char_w, $y_pos, 3172 $this->ndx_text_color, $leg, 'right'); 3173 // Draw a box in the data color 3174 ImageFilledRectangle($this->img, $dot_left_x, $y_pos + 1, $dot_right_x, 3175 $y_pos + $dot_height-1, $this->ndx_data_colors[$color_index]); 3176 // Draw a rectangle around the box 3177 ImageRectangle($this->img, $dot_left_x, $y_pos + 1, $dot_right_x, 3178 $y_pos + $dot_height-1, $this->ndx_text_color); 3179 3180 $y_pos += $char_h + $this->line_spacing; 3181 3182 $color_index++; 3183 if ($color_index > $max_color_index) 3184 $color_index = 0; 3185 } 3186 } // Function DrawLegend() 3187 3188 3189 /*! 3190 * TODO Draws a legend over (or below) an axis of the plot. 3191 */ 3192 function DrawAxisLegend() 3193 { 3194 // Calculate available room 3195 // Calculate length of all items (boxes included) 3196 // Calculate number of lines and room it would take. FIXME: this should be known in CalcMargins() 3197 // Draw. 3198 } 3199 3200///////////////////////////////////////////// 3201//////////////////// PLOT DRAWING 3202///////////////////////////////////////////// 3203 3204 3205 /*! 3206 * Draws a pie chart. Data has to be 'text-data' type. 3207 * 3208 * This can work in two ways: the classical, with a column for each sector 3209 * (computes the column totals and draws the pie with that) 3210 * OR 3211 * Takes each row as a sector and uses it's first value. This has the added 3212 * advantage of using the labels provided, which is not the case with the 3213 * former method. This might prove useful for pie charts from GROUP BY sql queries 3214 */ 3215 function DrawPieChart() 3216 { 3217 $xpos = $this->plot_area[0] + $this->plot_area_width/2; 3218 $ypos = $this->plot_area[1] + $this->plot_area_height/2; 3219 $diameter = min($this->plot_area_width, $this->plot_area_height); 3220 $radius = $diameter/2; 3221 3222 // Get sum of each column? One pie slice per column 3223 if ($this->data_type === 'text-data') { 3224 for ($i = 0; $i < $this->num_data_rows; $i++) { 3225 for ($j = 1; $j < $this->num_recs[$i]; $j++) { // Label ($row[0]) unused in these pie charts 3226 @ $sumarr[$j] += abs($this->data[$i][$j]); // NOTE! sum > 0 to make pie charts 3227 } 3228 } 3229 } 3230 // Or only one column per row, one pie slice per row? 3231 else if ($this->data_type == 'text-data-single') { 3232 for ($i = 0; $i < $this->num_data_rows; $i++) { 3233 $legend[$i] = $this->data[$i][0]; // Set the legend to column labels 3234 $sumarr[$i] = $this->data[$i][1]; 3235 } 3236 } 3237 else if ($this->data_type == 'data-data') { 3238 for ($i = 0; $i < $this->num_data_rows; $i++) { 3239 for ($j = 2; $j < $this->num_recs[$i]; $j++) { 3240 @ $sumarr[$j] += abs($this->data[$i][$j]); 3241 } 3242 } 3243 } 3244 else { 3245 $this->DrawError("DrawPieChart(): Data type '$this->data_type' not supported."); 3246 return FALSE; 3247 } 3248 3249 $total = array_sum($sumarr); 3250 3251 if ($total == 0) { 3252 $this->DrawError('DrawPieChart(): Empty data set'); 3253 return FALSE; 3254 } 3255 3256 if ($this->shading) { 3257 $diam2 = $diameter / 2; 3258 } else { 3259 $diam2 = $diameter; 3260 } 3261 $max_data_colors = count ($this->data_colors); 3262 3263 for ($h = $this->shading; $h >= 0; $h--) { 3264 $color_index = 0; 3265 $start_angle = 0; 3266 $end_angle = 0; 3267 foreach ($sumarr as $val) { 3268 // For shaded pies: the last one (at the top of the "stack") has a brighter color: 3269 if ($h == 0) 3270 $slicecol = $this->ndx_data_colors[$color_index]; 3271 else 3272 $slicecol = $this->ndx_data_dark_colors[$color_index]; 3273 3274 $label_txt = number_format(($val / $total * 100), $this->y_precision, '.', ', ') . '%'; 3275 $val = 360 * ($val / $total); 3276 3277 // NOTE that imagefilledarc measures angles CLOCKWISE (go figure why), 3278 // so the pie chart would start clockwise from 3 o'clock, would it not be 3279 // for the reversal of start and end angles in imagefilledarc() 3280 $start_angle = $end_angle; 3281 $end_angle += $val; 3282 $mid_angle = deg2rad($end_angle - ($val / 2)); 3283 3284 // Draw the slice 3285 ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2, 3286 360-$end_angle, 360-$start_angle, 3287 $slicecol, IMG_ARC_PIE); 3288 3289 // Draw the labels only once 3290 if ($h == 0) { 3291 // Draw the outline 3292 if (! $this->shading) 3293 ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2, 3294 360-$end_angle, 360-$start_angle, 3295 $this->ndx_grid_color, IMG_ARC_PIE | IMG_ARC_EDGED |IMG_ARC_NOFILL); 3296 3297 3298 // The '* 1.2' trick is to get labels out of the pie chart so there are more 3299 // chances they can be seen in small sectors. 3300 $label_x = $xpos + ($diameter * 1.2 * cos($mid_angle)) * $this->label_scale_position; 3301 $label_y = $ypos+$h - ($diam2 * 1.2 * sin($mid_angle)) * $this->label_scale_position; 3302 3303 $this->DrawText($this->generic_font, 0, $label_x, $label_y, $this->ndx_grid_color, 3304 $label_txt, 'center', 'center'); 3305 } 3306 $color_index++; 3307 $color_index = $color_index % $max_data_colors; 3308 } // end for 3309 } // end for 3310 } 3311 3312 3313 /*! 3314 * Supported data formats: data-data-error, text-data-error (doesn't exist yet) 3315 * ( data comes in as array("title", x, y, error+, error-, y2, error2+, error2-, ...) ) 3316 */ 3317 function DrawDotsError() 3318 { 3319 if ($this->data_type != 'data-data-error') { 3320 $this->DrawError("DrawDotsError(): Data type '$this->data_type' not supported."); 3321 return FALSE; 3322 } 3323 3324 // Suppress duplicate X data labels in linepoints mode; let DrawLinesError() do them. 3325 $do_labels = ($this->plot_type != 'linepoints'); 3326 3327 for($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3328 $record = 1; // Skip record #0 (title) 3329 3330 $x_now = $this->data[$row][$record++]; // Read it, advance record index 3331 3332 $x_now_pixels = $this->xtr($x_now); // Absolute coordinates. 3333 3334 // Draw X Data labels? 3335 if ($this->x_data_label_pos != 'none' && $do_labels) 3336 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row); 3337 3338 // Now go for Y, E+, E- 3339 for ($idx = 0; $record < $this->num_recs[$row]; $idx++) { 3340 // Y: 3341 $y_now = $this->data[$row][$record++]; 3342 $this->DrawDot($x_now, $y_now, $idx, $this->ndx_data_colors[$idx]); 3343 3344 // Error + 3345 $val = $this->data[$row][$record++]; 3346 $this->DrawYErrorBar($x_now, $y_now, $val, $this->error_bar_shape, 3347 $this->ndx_error_bar_colors[$idx]); 3348 // Error - 3349 $val = $this->data[$row][$record++]; 3350 $this->DrawYErrorBar($x_now, $y_now, -$val, $this->error_bar_shape, 3351 $this->ndx_error_bar_colors[$idx]); 3352 } 3353 } 3354 } // function DrawDotsError() 3355 3356 3357 /* 3358 * Supported data types: 3359 * - data-data ("title", x, y1, y2, y3, ...) 3360 * - text-data ("title", y1, y2, y3, ...) 3361 */ 3362 function DrawDots() 3363 { 3364 $this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__); 3365 3366 // Suppress duplicate X data labels in linepoints mode; let DrawLines() do them. 3367 $do_labels = ($this->plot_type != 'linepoints'); 3368 3369 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3370 $rec = 1; // Skip record #0 (data label) 3371 3372 // Do we have a value for X? 3373 if ($this->data_type == 'data-data') 3374 $x_now = $this->data[$row][$rec++]; // Read it, advance record index 3375 else 3376 $x_now = 0.5 + $cnt++; // Place text-data at X = 0.5, 1.5, 2.5, etc... 3377 3378 $x_now_pixels = $this->xtr($x_now); 3379 3380 // Draw X Data labels? 3381 if ($this->x_data_label_pos != 'none' && $do_labels) 3382 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row); 3383 3384 // Proceed with Y values 3385 for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) { 3386 if (is_numeric($this->data[$row][$rec])) { // Allow for missing Y data 3387 $this->DrawDot($x_now, $this->data[$row][$rec], 3388 $idx, $this->ndx_data_colors[$idx]); 3389 } 3390 } 3391 } 3392 } //function DrawDots 3393 3394 3395 /*! 3396 * A clean, fast routine for when you just want charts like stock volume charts 3397 */ 3398 function DrawThinBarLines() 3399 { 3400 $this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__); 3401 3402 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3403 $rec = 1; // Skip record #0 (data label) 3404 3405 // Do we have a value for X? 3406 if ($this->data_type == 'data-data') 3407 $x_now = $this->data[$row][$rec++]; // Read it, advance record index 3408 else 3409 $x_now = 0.5 + $cnt++; // Place text-data at X = 0.5, 1.5, 2.5, etc... 3410 3411 $x_now_pixels = $this->xtr($x_now); 3412 3413 // Draw X Data labels? 3414 if ($this->x_data_label_pos != 'none') 3415 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); 3416 3417 // Proceed with Y values 3418 for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) { 3419 if (is_numeric($this->data[$row][$rec])) { // Allow for missing Y data 3420 ImageSetThickness($this->img, $this->line_widths[$idx]); 3421 // Draws a line from user defined x axis position up to ytr($val) 3422 ImageLine($this->img, $x_now_pixels, $this->x_axis_y_pixels, $x_now_pixels, 3423 $this->ytr($this->data[$row][$rec]), $this->ndx_data_colors[$idx]); 3424 } 3425 } 3426 } 3427 3428 ImageSetThickness($this->img, 1); 3429 } //function DrawThinBarLines 3430 3431 /*! 3432 * 3433 */ 3434 function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color) 3435 { 3436 /* 3437 // TODO: add a parameter to show datalabels next to error bars? 3438 // something like this: 3439 if ($this->x_data_label_pos == 'plot') { 3440 $this->DrawText($this->error_font, 90, $x1, $y2, 3441 $color, $label, 'center', 'top'); 3442 */ 3443 3444 $x1 = $this->xtr($x_world); 3445 $y1 = $this->ytr($y_world); 3446 $y2 = $this->ytr($y_world+$error_height) ; 3447 3448 ImageSetThickness($this->img, $this->error_bar_line_width); 3449 ImageLine($this->img, $x1, $y1 , $x1, $y2, $color); 3450 3451 switch ($error_bar_type) { 3452 case 'line': 3453 break; 3454 case 'tee': 3455 ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color); 3456 break; 3457 default: 3458 ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color); 3459 break; 3460 } 3461 3462 ImageSetThickness($this->img, 1); 3463 return TRUE; 3464 } 3465 3466 /*! 3467 * Draws a styled dot. Uses world coordinates. 3468 * Supported types: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot', 3469 * 'diamond', 'triangle', 'trianglemid' 3470 */ 3471 function DrawDot($x_world, $y_world, $record, $color) 3472 { 3473 // TODO: optimize, avoid counting every time we are called. 3474 $record = $record % count ($this->point_shapes); 3475 3476 $half_point = $this->point_sizes[$record] / 2; 3477 3478 $x_mid = $this->xtr($x_world); 3479 $y_mid = $this->ytr($y_world); 3480 3481 $x1 = $x_mid - $half_point; 3482 $x2 = $x_mid + $half_point; 3483 $y1 = $y_mid - $half_point; 3484 $y2 = $y_mid + $half_point; 3485 3486 switch ($this->point_shapes[$record]) { 3487 case 'halfline': 3488 ImageLine($this->img, $x1, $y_mid, $x_mid, $y_mid, $color); 3489 break; 3490 case 'line': 3491 ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color); 3492 break; 3493 case 'plus': 3494 ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color); 3495 ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color); 3496 break; 3497 case 'cross': 3498 ImageLine($this->img, $x1, $y1, $x2, $y2, $color); 3499 ImageLine($this->img, $x1, $y2, $x2, $y1, $color); 3500 break; 3501 case 'rect': 3502 ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color); 3503 break; 3504 case 'circle': 3505 ImageArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record], $this->point_sizes[$record], 0, 360, $color); 3506 break; 3507 case 'dot': 3508 ImageFilledArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record], $this->point_sizes[$record], 0, 360, 3509 $color, IMG_ARC_PIE); 3510 break; 3511 case 'diamond': 3512 $arrpoints = array( $x1, $y_mid, $x_mid, $y1, $x2, $y_mid, $x_mid, $y2); 3513 ImageFilledPolygon($this->img, $arrpoints, 4, $color); 3514 break; 3515 case 'triangle': 3516 $arrpoints = array( $x1, $y_mid, $x2, $y_mid, $x_mid, $y2); 3517 ImageFilledPolygon($this->img, $arrpoints, 3, $color); 3518 break; 3519 case 'trianglemid': 3520 $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y_mid); 3521 ImageFilledPolygon($this->img, $arrpoints, 3, $color); 3522 break; 3523 case 'none': 3524 break; 3525 default: 3526 ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color); 3527 break; 3528 } 3529 return TRUE; 3530 } 3531 3532 /*! 3533 * Draw an area plot. Supported data types: 3534 * 'text-data' 3535 * 'data-data' 3536 * NOTE: This function used to add first and last data values even on incomplete 3537 * sets. That is not the behaviour now. As for missing data in between, 3538 * there are two posibilities: replace the point with one on the X axis (previous 3539 * way), or forget about it and use the preceding and following ones to draw the polygon. 3540 * There is the possibility to use both, we just need to add the method to set 3541 * it. Something like SetMissingDataBehaviour(), for example. 3542 */ 3543 function DrawArea() 3544 { 3545 $incomplete_data_defaults_to_x_axis = FALSE; // TODO: make this configurable 3546 3547 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3548 $rec = 1; // Skip record #0 (data label) 3549 3550 if ($this->data_type == 'data-data') // Do we have a value for X? 3551 $x_now = $this->data[$row][$rec++]; // Read it, advance record index 3552 else 3553 $x_now = 0.5 + $cnt++; // Place text-data at X = 0.5, 1.5, 2.5, etc... 3554 3555 $x_now_pixels = $this->xtr($x_now); // Absolute coordinates 3556 3557 3558 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3559 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); 3560 3561 // Proceed with Y values 3562 // Create array of points for imagefilledpolygon() 3563 for($idx = 0; $rec < $this->num_recs[$row]; $rec++, $idx++) { 3564 if (is_numeric($this->data[$row][$rec])) { // Allow for missing Y data 3565 $y_now_pixels = $this->ytr($this->data[$row][$rec]); 3566 3567 $posarr[$idx][] = $x_now_pixels; 3568 $posarr[$idx][] = $y_now_pixels; 3569 3570 $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1; 3571 } 3572 // If there's missing data... 3573 else { 3574 if (isset ($incomplete_data_defaults_to_x_axis)) { 3575 $posarr[$idx][] = $x_now_pixels; 3576 $posarr[$idx][] = $this->x_axis_y_pixels; 3577 $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1; 3578 } 3579 } 3580 } 3581 } // end for 3582 3583 $end = count($posarr); 3584 for ($i = 0; $i < $end; $i++) { 3585 // Prepend initial points. X = first point's X, Y = x_axis_y_pixels 3586 $x = $posarr[$i][0]; 3587 array_unshift($posarr[$i], $x, $this->x_axis_y_pixels); 3588 3589 // Append final points. X = last point's X, Y = x_axis_y_pixels 3590 $x = $posarr[$i][count($posarr[$i])-2]; 3591 array_push($posarr[$i], $x, $this->x_axis_y_pixels); 3592 3593 $num_points[$i] += 2; 3594 3595 // Draw the poligon 3596 ImageFilledPolygon($this->img, $posarr[$i], $num_points[$i], $this->ndx_data_colors[$i]); 3597 } 3598 3599 } // function DrawArea() 3600 3601 3602 /*! 3603 * Draw Lines. Supported data-types: 3604 * 'data-data', 3605 * 'text-data' 3606 * NOTE: Please see the note regarding incomplete data sets on DrawArea() 3607 */ 3608 function DrawLines() 3609 { 3610 // This will tell us if lines have already begun to be drawn. 3611 // It is an array to keep separate information for every line, with a single 3612 // variable we would sometimes get "undefined offset" errors and no plot... 3613 $start_lines = array_fill(0, $this->records_per_group, FALSE); 3614 3615 if ($this->data_type == 'text-data') { 3616 $lastx[0] = $this->xtr(0); 3617 $lasty[0] = $this->xtr(0); 3618 } 3619 3620 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3621 $record = 1; // Skip record #0 (data label) 3622 3623 if ($this->data_type == 'data-data') // Do we have a value for X? 3624 $x_now = $this->data[$row][$record++]; // Read it, advance record index 3625 else 3626 $x_now = 0.5 + $cnt++; // Place text-data at X = 0.5, 1.5, 2.5, etc... 3627 3628 $x_now_pixels = $this->xtr($x_now); // Absolute coordinates 3629 3630 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3631 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row); 3632 3633 for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) { 3634 if (($line_style = $this->line_styles[$idx]) == 'none') 3635 continue; //Allow suppressing entire line, useful with linepoints 3636 if (is_numeric($this->data[$row][$record])) { //Allow for missing Y data 3637 $y_now_pixels = $this->ytr($this->data[$row][$record]); 3638 3639 if ($start_lines[$idx] == TRUE) { 3640 // Set line width, revert it to normal at the end 3641 ImageSetThickness($this->img, $this->line_widths[$idx]); 3642 3643 if ($line_style == 'dashed') { 3644 $this->SetDashedStyle($this->ndx_data_colors[$idx]); 3645 ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 3646 IMG_COLOR_STYLED); 3647 } else { 3648 ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 3649 $this->ndx_data_colors[$idx]); 3650 } 3651 3652 } 3653 $lasty[$idx] = $y_now_pixels; 3654 $lastx[$idx] = $x_now_pixels; 3655 $start_lines[$idx] = TRUE; 3656 } 3657 // Y data missing... should we leave a blank or not? 3658 else if ($this->draw_broken_lines) { 3659 $start_lines[$idx] = FALSE; 3660 } 3661 } // end for 3662 } // end for 3663 3664 ImageSetThickness($this->img, 1); // Revert to original state for lines to be drawn later. 3665 } // function DrawLines() 3666 3667 3668 /*! 3669 * Draw lines with error bars - data comes in as 3670 * array("label", x, y, error+, error-, y2, error2+, error2-, ...); 3671 */ 3672 function DrawLinesError() 3673 { 3674 if ($this->data_type != 'data-data-error') { 3675 $this->DrawError("DrawLinesError(): Data type '$this->data_type' not supported."); 3676 return FALSE; 3677 } 3678 3679 $start_lines = array_fill(0, $this->records_per_group, FALSE); 3680 3681 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3682 $record = 1; // Skip record #0 (data label) 3683 3684 $x_now = $this->data[$row][$record++]; // Read X value, advance record index 3685 3686 $x_now_pixels = $this->xtr($x_now); // Absolute coordinates. 3687 3688 3689 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3690 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row); 3691 3692 // Now go for Y, E+, E- 3693 for ($idx = 0; $record < $this->num_recs[$row]; $idx++) { 3694 if (($line_style = $this->line_styles[$idx]) == 'none') 3695 continue; //Allow suppressing entire line, useful with linepoints 3696 // Y 3697 $y_now = $this->data[$row][$record++]; 3698 $y_now_pixels = $this->ytr($y_now); 3699 3700 if ($start_lines[$idx] == TRUE) { 3701 ImageSetThickness($this->img, $this->line_widths[$idx]); 3702 3703 if ($line_style == 'dashed') { 3704 $this->SetDashedStyle($this->ndx_data_colors[$idx]); 3705 ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 3706 IMG_COLOR_STYLED); 3707 } else { 3708 ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 3709 $this->ndx_data_colors[$idx]); 3710 } 3711 } 3712 3713 // Error+ 3714 $val = $this->data[$row][$record++]; 3715 $this->DrawYErrorBar($x_now, $y_now, $val, $this->error_bar_shape, 3716 $this->ndx_error_bar_colors[$idx]); 3717 3718 // Error- 3719 $val = $this->data[$row][$record++]; 3720 $this->DrawYErrorBar($x_now, $y_now, -$val, $this->error_bar_shape, 3721 $this->ndx_error_bar_colors[$idx]); 3722 3723 // Update indexes: 3724 $start_lines[$idx] = TRUE; // Tells us if we already drew the first column of points, 3725 // thus having $lastx and $lasty ready for the next column. 3726 $lastx[$idx] = $x_now_pixels; 3727 $lasty[$idx] = $y_now_pixels; 3728 } // end while 3729 } // end for 3730 3731 ImageSetThickness($this->img, 1); // Revert to original state for lines to be drawn later. 3732 } // function DrawLinesError() 3733 3734 3735 3736 /*! 3737 * This is a mere copy of DrawLines() with one more line drawn for each point 3738 */ 3739 function DrawSquared() 3740 { 3741 // This will tell us if lines have already begun to be drawn. 3742 // It is an array to keep separate information for every line, for with a single 3743 // variable we could sometimes get "undefined offset" errors and no plot... 3744 $start_lines = array_fill(0, $this->records_per_group, FALSE); 3745 3746 if ($this->data_type == 'text-data') { 3747 $lastx[0] = $this->xtr(0); 3748 $lasty[0] = $this->xtr(0); 3749 } 3750 3751 for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) { 3752 $record = 1; // Skip record #0 (data label) 3753 3754 if ($this->data_type == 'data-data') // Do we have a value for X? 3755 $x_now = $this->data[$row][$record++]; // Read it, advance record index 3756 else 3757 $x_now = 0.5 + $cnt++; // Place text-data at X = 0.5, 1.5, 2.5, etc... 3758 3759 $x_now_pixels = $this->xtr($x_now); // Absolute coordinates 3760 3761 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3762 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); // notice there is no last param. 3763 3764 // Draw Lines 3765 for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) { 3766 if (is_numeric($this->data[$row][$record])) { // Allow for missing Y data 3767 $y_now_pixels = $this->ytr($this->data[$row][$record]); 3768 3769 if ($start_lines[$idx] == TRUE) { 3770 // Set line width, revert it to normal at the end 3771 ImageSetThickness($this->img, $this->line_widths[$idx]); 3772 3773 if ($this->line_styles[$idx] == 'dashed') { 3774 $this->SetDashedStyle($this->ndx_data_colors[$idx]); 3775 ImageLine($this->img, $lastx[$idx], $lasty[$idx], $x_now_pixels, $lasty[$idx], 3776 IMG_COLOR_STYLED); 3777 ImageLine($this->img, $x_now_pixels, $lasty[$idx], $x_now_pixels, $y_now_pixels, 3778 IMG_COLOR_STYLED); 3779 } else { 3780 ImageLine($this->img, $lastx[$idx], $lasty[$idx], $x_now_pixels, $lasty[$idx], 3781 $this->ndx_data_colors[$idx]); 3782 ImageLine($this->img, $x_now_pixels, $lasty[$idx], $x_now_pixels, $y_now_pixels, 3783 $this->ndx_data_colors[$idx]); 3784 } 3785 } 3786 $lastx[$idx] = $x_now_pixels; 3787 $lasty[$idx] = $y_now_pixels; 3788 $start_lines[$idx] = TRUE; 3789 } 3790 // Y data missing... should we leave a blank or not? 3791 else if ($this->draw_broken_lines) { 3792 $start_lines[$idx] = FALSE; 3793 } 3794 } 3795 } // end while 3796 3797 ImageSetThickness($this->img, 1); 3798 } // function DrawSquared() 3799 3800 3801 /*! 3802 * Data comes in as array("title", x, y, y2, y3, ...) 3803 */ 3804 function DrawBars() 3805 { 3806 if ($this->data_type != 'text-data') { 3807 $this->DrawError('DrawBars(): Bar plots must be text-data: use function SetDataType("text-data")'); 3808 return FALSE; 3809 } 3810 3811 // This is the X offset from the bar group's label center point to the left side of the first bar 3812 // in the group. See also CalcBarWidths above. 3813 $x_first_bar = (($this->records_per_group - 1) * $this->record_bar_width) / 2 - $this->bar_adjust_gap; 3814 3815 for ($row = 0; $row < $this->num_data_rows; $row++) { 3816 $record = 1; // Skip record #0 (data label) 3817 3818 $x_now_pixels = $this->xtr(0.5 + $row); // Place text-data at X = 0.5, 1.5, 2.5, etc... 3819 3820 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3821 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); 3822 3823 // Lower left X of first bar in the group: 3824 $x1 = $x_now_pixels - $x_first_bar; 3825 3826 // Draw the bars in the group: 3827 for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) { 3828 if (is_numeric($this->data[$row][$record])) { // Allow for missing Y data 3829 $x2 = $x1 + $this->actual_bar_width; 3830 3831 if ($this->data[$row][$record] < $this->x_axis_position) { 3832 $y1 = $this->x_axis_y_pixels; 3833 $y2 = $this->ytr($this->data[$row][$record]); 3834 $upgoing_bar = False; 3835 } else { 3836 $y1 = $this->ytr($this->data[$row][$record]); 3837 $y2 = $this->x_axis_y_pixels; 3838 $upgoing_bar = True; 3839 } 3840 3841 // Draw the bar 3842 ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $this->ndx_data_colors[$idx]); 3843 3844 if ($this->shading) { // Draw the shade? 3845 ImageFilledPolygon($this->img, array($x1, $y1, 3846 $x1 + $this->shading, $y1 - $this->shading, 3847 $x2 + $this->shading, $y1 - $this->shading, 3848 $x2 + $this->shading, $y2 - $this->shading, 3849 $x2, $y2, 3850 $x2, $y1), 3851 6, $this->ndx_data_dark_colors[$idx]); 3852 } 3853 // Or draw a border? 3854 else { 3855 ImageRectangle($this->img, $x1, $y1, $x2,$y2, $this->ndx_data_border_colors[$idx]); 3856 } 3857 3858 // Draw optional data labels above the bars (or below, for negative values). 3859 if ( $this->y_data_label_pos == 'plotin') { 3860 if ($upgoing_bar) { 3861 $v_align = 'top'; 3862 $y_offset = -5 - $this->shading; 3863 } else { 3864 $v_align = 'bottom'; 3865 $y_offset = 2; 3866 } 3867 $this->DrawDataLabel('', NULL, $row+0.5, $this->data[$row][$record], '', 3868 $this->data[$row][$record], 'center', $v_align, 3869 ($idx + 0.5) * $this->record_bar_width - $x_first_bar, $y_offset); 3870 } 3871 3872 } 3873 // Step to next bar in group: 3874 $x1 += $this->record_bar_width; 3875 } // end for 3876 } // end for 3877 } //function DrawBars 3878 3879 3880 /*! 3881 * Data comes in as array("title", x, y, y2, y3, ...) 3882 * \note Original stacked bars idea by Laurent Kruk < lolok at users.sourceforge.net > 3883 */ 3884 function DrawStackedBars() 3885 { 3886 if ($this->data_type != 'text-data') { 3887 $this->DrawError('DrawStackedBars(): Bar plots must be text-data: use SetDataType("text-data")'); 3888 return FALSE; 3889 } 3890 3891 // This is the X offset from the bar's label center point to the left side of the bar. 3892 $x_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap; 3893 3894 for ($row = 0; $row < $this->num_data_rows; $row++) { 3895 $record = 1; // Skip record #0 (data label) 3896 3897 $x_now_pixels = $this->xtr(0.5 + $row); // Place text-data at X = 0.5, 1.5, 2.5, etc... 3898 3899 if ($this->x_data_label_pos != 'none') // Draw X Data labels? 3900 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); 3901 3902 // Lower left and lower right X of the bars in this group: 3903 $x1 = $x_now_pixels - $x_first_bar; 3904 $x2 = $x1 + $this->actual_bar_width; 3905 3906 // Draw the bars 3907 $oldv = 0; 3908 for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) { 3909 if (is_numeric($this->data[$row][$record])) { // Allow for missing Y data 3910 3911 $y1 = $this->ytr(abs($this->data[$row][$record]) + $oldv); 3912 $y2 = $this->ytr($this->x_axis_position + $oldv); 3913 $oldv += abs($this->data[$row][$record]); 3914 3915 // Draw the bar 3916 ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $this->ndx_data_colors[$idx]); 3917 3918 if ($this->shading) { // Draw the shade? 3919 ImageFilledPolygon($this->img, array($x1, $y1, 3920 $x1 + $this->shading, $y1 - $this->shading, 3921 $x2 + $this->shading, $y1 - $this->shading, 3922 $x2 + $this->shading, $y2 - $this->shading, 3923 $x2, $y2, 3924 $x2, $y1), 3925 6, $this->ndx_data_dark_colors[$idx]); 3926 } 3927 // Or draw a border? 3928 else { 3929 ImageRectangle($this->img, $x1, $y1, $x2,$y2, $this->ndx_data_border_colors[$idx]); 3930 } 3931 } 3932 } // end for 3933 } // end for 3934 } //function DrawStackedBars 3935 3936 3937 /*! 3938 * 3939 */ 3940 function DrawGraph() 3941 { 3942 if (! $this->img) { 3943 $this->DrawError('DrawGraph(): No image resource allocated'); 3944 return FALSE; 3945 } 3946 3947 if (empty($this->data) || ! is_array($this->data)) { 3948 $this->DrawError("DrawGraph(): No data array"); 3949 return FALSE; 3950 } 3951 3952 if (! isset($this->data_limits_done)) 3953 $this->FindDataLimits(); // Get maxima and minima for scaling 3954 3955 if ($this->total_records == 0) { // Check for empty data sets 3956 $this->DrawError('Empty data set'); 3957 return FALSE; 3958 } 3959 3960 $this->CalcMargins(); // Calculate margins 3961 3962 if (! isset($this->plot_area_width)) { // Set plot area pixel values (plot_area[]) 3963 if ($this->plot_type == 'pie') { 3964 // Pie charts can maximize image space usage. 3965 $this->SetPlotAreaPixels($this->safe_margin, $this->title_height + $this->safe_margin, 3966 $this->image_width - $this->safe_margin, 3967 $this->image_height - $this->safe_margin); 3968 } else { 3969 $this->SetPlotAreaPixels(); 3970 } 3971 } 3972 3973 if (! isset($this->plot_max_y)) // Set plot area world values (plot_max_x, etc.) 3974 $this->SetPlotAreaWorld(); 3975 3976 /* FIXME!! this sort of thing should not be done without user's consent 3977 if ($this->x_data_label_pos != 'none') { // Default: do not draw tick stuff if 3978 $this->x_tick_label_pos = 'none'; // there are data labels. 3979 $this->x_tick_pos = 'none'; 3980 } 3981 */ 3982 $this->PadArrays(); // Pad color and style arrays to fit records per group. 3983 3984 $this->DrawBackground(); 3985 3986 $this->DrawImageBorder(); 3987 3988 $this->DrawPlotAreaBackground(); 3989 3990 $this->DrawTitle(); 3991 $this->DrawXTitle(); 3992 $this->DrawYTitle(); 3993 3994 // Pie charts are drawn differently, handle them first 3995 if ($this->plot_type == 'pie') { 3996 $this->DrawPieChart(); 3997 3998 if ($this->legend) 3999 $this->DrawLegend($this->legend_x_pos, $this->legend_y_pos, ''); 4000 4001 if ($this->print_image) 4002 $this->PrintImage(); 4003 4004 return; 4005 } 4006 4007 ////// All other chart types: 4008 4009 if (! $this->grid_at_foreground) { // Usually one wants grids to go back, but... 4010 $this->DrawYAxis(); // Y axis must be drawn before X axis (see DrawYAxis()) 4011 $this->DrawXAxis(); 4012 } 4013 4014 switch ($this->plot_type) { 4015 case 'thinbarline': 4016 $this->DrawThinBarLines(); 4017 break; 4018 case 'area': 4019 $this->DrawArea(); 4020 break; 4021 case 'squared': 4022 $this->DrawSquared(); 4023 break; 4024 case 'lines': 4025 if ( $this->data_type == 'data-data-error') { 4026 $this->DrawLinesError(); 4027 } else { 4028 $this->DrawLines(); 4029 } 4030 break; 4031 case 'linepoints': 4032 if ( $this->data_type == 'data-data-error') { 4033 $this->DrawLinesError(); 4034 $this->DrawDotsError(); 4035 } else { 4036 $this->DrawLines(); 4037 $this->DrawDots(); 4038 } 4039 break; 4040 case 'points'; 4041 if ( $this->data_type == 'data-data-error') { 4042 $this->DrawDotsError(); 4043 } else { 4044 $this->DrawDots(); 4045 } 4046 break; 4047 case 'stackedbars': 4048 $this->CalcBarWidths(); 4049 $this->DrawStackedBars(); 4050 break; 4051 case 'bars': 4052 default: 4053 $this->plot_type = 'bars'; // Set it if it wasn't already set. (necessary?) 4054 $this->CalcBarWidths(); 4055 $this->DrawBars(); 4056 break; 4057 } // end switch 4058 4059 if ($this->grid_at_foreground) { // Usually one wants grids to go back, but... 4060 $this->DrawYAxis(); // Y axis must be drawn before X axis (see DrawYAxis()) 4061 $this->DrawXAxis(); 4062 } 4063 4064 $this->DrawPlotBorder(); 4065 4066 if ($this->legend) 4067 $this->DrawLegend($this->legend_x_pos, $this->legend_y_pos, ''); 4068 4069 if ($this->print_image) 4070 $this->PrintImage(); 4071 4072 } //function DrawGraph() 4073 4074///////////////////////////////////////////// 4075////////////////// DEPRECATED METHODS 4076///////////////////////////////////////////// 4077 4078 /*! 4079 * Deprecated, use SetYTickPos() 4080 */ 4081 function SetDrawVertTicks($which_dvt) 4082 { 4083 if ($which_dvt != 1) 4084 $this->SetYTickPos('none'); 4085 return TRUE; 4086 } 4087 4088 /*! 4089 * Deprecated, use SetXTickPos() 4090 */ 4091 function SetDrawHorizTicks($which_dht) 4092 { 4093 if ($which_dht != 1) 4094 $this->SetXTickPos('none'); 4095 return TRUE; 4096 } 4097 4098 /*! 4099 * \deprecated Use SetNumXTicks() 4100 */ 4101 function SetNumHorizTicks($n) 4102 { 4103 return $this->SetNumXTicks($n); 4104 } 4105 4106 /*! 4107 * \deprecated Use SetNumYTicks() 4108 */ 4109 function SetNumVertTicks($n) 4110 { 4111 return $this->SetNumYTicks($n); 4112 } 4113 4114 /*! 4115 * \deprecated Use SetXTickIncrement() 4116 */ 4117 function SetHorizTickIncrement($inc) 4118 { 4119 return $this->SetXTickIncrement($inc); 4120 } 4121 4122 4123 /*! 4124 * \deprecated Use SetYTickIncrement() 4125 */ 4126 function SetVertTickIncrement($inc) 4127 { 4128 return $this->SetYTickIncrement($inc); 4129 } 4130 4131 /*! 4132 * \deprecated Use SetYTickPos() 4133 */ 4134 function SetVertTickPosition($which_tp) 4135 { 4136 return $this->SetYTickPos($which_tp); 4137 } 4138 4139 /*! 4140 * \deprecated Use SetXTickPos() 4141 */ 4142 function SetHorizTickPosition($which_tp) 4143 { 4144 return $this->SetXTickPos($which_tp); 4145 } 4146 4147 /*! 4148 * \deprecated Use SetFont() 4149 */ 4150 function SetTitleFontSize($which_size) 4151 { 4152 return $this->SetFont('title', $which_size); 4153 } 4154 4155 /*! 4156 * \deprecated Use SetFont() 4157 */ 4158 function SetAxisFontSize($which_size) 4159 { 4160 $this->SetFont('x_label', $which_size); 4161 $this->SetFont('y_label', $which_size); 4162 } 4163 4164 /*! 4165 * \deprecated Use SetFont() 4166 */ 4167 function SetSmallFontSize($which_size) 4168 { 4169 return $this->SetFont('generic', $which_size); 4170 } 4171 4172 /*! 4173 * \deprecated Use SetFont() 4174 */ 4175 function SetXLabelFontSize($which_size) 4176 { 4177 return $this->SetFont('x_title', $which_size); 4178 } 4179 4180 /*! 4181 * \deprecated Use SetFont() 4182 */ 4183 function SetYLabelFontSize($which_size) 4184 { 4185 return $this->SetFont('y_title', $which_size); 4186 } 4187 4188 /*! 4189 * \deprecated Use SetXTitle() 4190 */ 4191 function SetXLabel($which_xlab) 4192 { 4193 return $this->SetXTitle($which_xlab); 4194 } 4195 4196 /*! 4197 * \deprecated Use SetYTitle() 4198 */ 4199 function SetYLabel($which_ylab) 4200 { 4201 return $this->SetYTitle($which_ylab); 4202 } 4203 4204 /*! 4205 * \deprecated This is now an Internal function - please set width and 4206 * height via PHPlot() upon object construction 4207 */ 4208 function SetImageArea($which_iw, $which_ih) 4209 { 4210 $this->image_width = $which_iw; 4211 $this->image_height = $which_ih; 4212 4213 return TRUE; 4214 } 4215 4216 /*! 4217 * \deprecated Use SetXTickLength() and SetYTickLength() instead. 4218 */ 4219 function SetTickLength($which_tl) 4220 { 4221 $this->SetXTickLength($which_tl); 4222 $this->SetYTickLength($which_tl); 4223 return TRUE; 4224 } 4225 4226 /*! 4227 * \deprecated Use SetYLabelType() 4228 */ 4229 function SetYGridLabelType($which_yglt) 4230 { 4231 return $this->SetYLabelType($which_yglt); 4232 } 4233 4234 /*! 4235 * \deprecated Use SetXLabelType() 4236 */ 4237 function SetXGridLabelType($which_xglt) 4238 { 4239 return $this->SetXLabelType($which_xglt); 4240 } 4241 /*! 4242 * \deprecated Use SetYTickLabelPos() 4243 */ 4244 function SetYGridLabelPos($which_yglp) 4245 { 4246 return $this->SetYTickLabelPos($which_yglp); 4247 } 4248 /*! 4249 * \deprecated Use SetXTickLabelPos() 4250 */ 4251 function SetXGridLabelPos($which_xglp) 4252 { 4253 return $this->SetXTickLabelPos($which_xglp); 4254 } 4255 4256 4257 /*! 4258 * \deprecated Use SetXtitle() 4259 */ 4260 function SetXTitlePos($xpos) 4261 { 4262 $this->x_title_pos = $xpos; 4263 return TRUE; 4264 } 4265 4266 /*! 4267 * \deprecated Use SetYTitle() 4268 */ 4269 function SetYTitlePos($xpos) 4270 { 4271 $this->y_title_pos = $xpos; 4272 return TRUE; 4273 } 4274 4275 /*! 4276 * \deprecated Use DrawDots() 4277 */ 4278 function DrawDotSeries() 4279 { 4280 $this->DrawDots(); 4281 } 4282 4283 /*! 4284 * \deprecated Use SetXLabelAngle() 4285 */ 4286 function SetXDataLabelAngle($which_xdla) 4287 { 4288 return $this->SetXLabelAngle($which_xdla); 4289 } 4290 4291 /*! 4292 * Draw Labels (not grid labels) on X Axis, following data points. Default position is 4293 * down of plot. Care must be taken not to draw these and x_tick_labels as they'd probably overlap. 4294 * 4295 * \deprecated Use SetXDataLabelPos() 4296 */ 4297 function SetDrawXDataLabels($which_dxdl) 4298 { 4299 if ($which_dxdl == '1' ) 4300 $this->SetXDataLabelPos('plotdown'); 4301 else 4302 $this->SetXDataLabelPos('none'); 4303 } 4304 4305 /*! 4306 * \deprecated This method was intended to improve performance by being specially 4307 * written for 'data-data'. However, the improvement didn't pay. Use DrawLines() instead 4308 */ 4309 function DrawLineSeries() 4310 { 4311 return $this->DrawLines(); 4312 } 4313 4314 /*! 4315 * \deprecated Calculates maximum X-Axis label height. Now inside CalcMargins() 4316 */ 4317 function CalcXHeights() 4318 { 4319 // TTF 4320 if ($this->use_ttf) { 4321 $xstr = str_repeat('.', $this->max_t); 4322 $size = $this->TTFBBoxSize($this->x_label_font['size'], $this->x_label_angle, 4323 $this->x_label_font['font'], $xstr); 4324 $this->x_tick_label_height = $size[1]; 4325 } 4326 // Fixed font 4327 else { // For Non-TTF fonts we can have only angles 0 or 90 4328 if ($this->x_label_angle == 90) 4329 $this->x_tick_label_height = $this->max_t * $this->x_label_font['width']; 4330 else 4331 $this->x_tick_label_height = $this->x_label_font['height']; 4332 } 4333 4334 return TRUE; 4335 } 4336 4337 4338 /*! 4339 * \deprecated Calculates Maximum Y-Axis tick label width. Now inside CalcMargins() 4340 */ 4341 function CalcYWidths() 4342 { 4343 //the "." is for space. It isn't actually printed 4344 $ylab = number_format($this->max_y, $this->y_precision, '.', ', ') . $this->data_units_text . '.'; 4345 4346 // TTF 4347 if ($this->use_ttf) { 4348 // Maximum Y tick label width 4349 $size = $this->TTFBBoxSize($this->y_label_font['size'], 0, $this->y_label_font['font'], $ylab); 4350 $this->y_tick_label_width = $size[0]; 4351 4352 } 4353 // Fixed font 4354 else { 4355 // Y axis title width 4356 $this->y_tick_label_width = strlen($ylab) * $this->y_label_font['width']; 4357 } 4358 4359 return TRUE; 4360 } 4361 4362 /*! 4363 * \deprecated Superfluous. 4364 */ 4365 function DrawLabels() 4366 { 4367 $this->DrawTitle(); 4368 $this->DrawXTitle(); 4369 $this->DrawYTitle(); 4370 } 4371 4372 /*! 4373 * Set up the image resource 'img' 4374 * \deprecated The constructor should init 'img' 4375 */ 4376 function InitImage() 4377 { 4378 $this->img = ImageCreate($this->image_width, $this->image_height); 4379 4380 if (! $this->img) 4381 $this->PrintError('InitImage(): Could not create image resource'); 4382 return TRUE; 4383 } 4384 4385 /*! 4386 * \deprecated 4387 */ 4388 function SetNewPlotAreaPixels($x1, $y1, $x2, $y2) 4389 { 4390 //Like in GD 0, 0 is upper left set via pixel Coordinates 4391 $this->plot_area = array($x1, $y1, $x2, $y2); 4392 $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0]; 4393 $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1]; 4394 $this->y_top_margin = $this->plot_area[1]; 4395 4396 if (isset($this->plot_max_x)) 4397 $this->CalcTranslation(); 4398 4399 return TRUE; 4400 } 4401 4402 /*! 4403 * \deprecated Use _SetRGBColor() 4404 */ 4405 function SetColor($which_color) 4406 { 4407 $this->SetRGBColor($which_color); 4408 return TRUE; 4409 } 4410 4411 /* 4412 * \deprecated Use SetLineWidths(). 4413 */ 4414 function SetLineWidth($which_lw) 4415 { 4416 4417 $this->SetLineWidths($which_lw); 4418 4419 if (!$this->error_bar_line_width) { 4420 $this->SetErrorBarLineWidth($which_lw); 4421 } 4422 return TRUE; 4423 } 4424 4425 /*! 4426 * \deprecated 4427 */ 4428 function DrawDashedLine($x1, $y1, $x2, $y2 , $dash_length, $dash_space, $color) 4429 { 4430 if ($dash_length) 4431 $dashes = array_fill(0, $dash_length, $color); 4432 else 4433 $dashes = array(); 4434 if ($dash_space) 4435 $spaces = array_fill(0, $dash_space, IMG_COLOR_TRANSPARENT); 4436 else 4437 $spaces = array(); 4438 4439 $style = array_merge($dashes, $spaces); 4440 ImageSetStyle($this->img, $style); 4441 ImageLine($this->img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); 4442 } 4443 4444 /*! 4445 * \deprecated Selects an input file to be used as background for the whole graph. 4446 * This resizes the graph to the image's size. 4447 */ 4448 function SetInputFile($which_input_file) 4449 { 4450 $size = GetImageSize($which_input_file); 4451 $input_type = $size[2]; 4452 4453 switch($input_type) { 4454 case 1: 4455 $im = @ ImageCreateFromGIF ($which_input_file); 4456 if (!$im) { // See if it failed 4457 $this->PrintError("Unable to open $which_input_file as a GIF"); 4458 return FALSE; 4459 } 4460 break; 4461 case 3: 4462 $im = @ ImageCreateFromPNG ($which_input_file); 4463 if (!$im) { // See if it failed 4464 $this->PrintError("Unable to open $which_input_file as a PNG"); 4465 return FALSE; 4466 } 4467 break; 4468 case 2: 4469 $im = @ ImageCreateFromJPEG ($which_input_file); 4470 if (!$im) { // See if it failed 4471 $this->PrintError("Unable to open $which_input_file as a JPG"); 4472 return FALSE; 4473 } 4474 break; 4475 default: 4476 $this->PrintError('SetInputFile(): Please select gif, jpg, or png for image type!'); 4477 return FALSE; 4478 break; 4479 } 4480 4481 // Set Width and Height of Image 4482 $this->image_width = $size[0]; 4483 $this->image_height = $size[1]; 4484 4485 // Deallocate any resources previously allocated 4486 if (isset($this->img)) 4487 imagedestroy($this->img); 4488 4489 $this->img = $im; 4490 4491 // Do not overwrite the input file with the background color. 4492 $this->background_done = TRUE; 4493 4494 return TRUE; 4495 4496 } 4497 4498 4499 /* 4500 * \deprecated Use SetPointShapes(). 4501 */ 4502 function SetPointShape($which_pt) 4503 { 4504 $this->SetPointShapes($which_pt); 4505 return TRUE; 4506 } 4507 4508 /* 4509 * \deprecated Use SetPointSizes(). 4510 */ 4511 function SetPointSize($which_ps) 4512 { 4513 $this->SetPointSizes($which_ps); 4514 return TRUE; 4515 } 4516} // class PHPlot 4517 4518 4519 4520//////////////////////// 4521 4522 4523/*! 4524 * Pads an array with another or with itself. 4525 * \param arr array Original array (reference) 4526 * \param size int Size of the resulting array. 4527 * \param arr2 array If specified, array to use for padding. If unspecified, pad with $arr. 4528 */ 4529function array_pad_array(&$arr, $size, $arr2=NULL) 4530{ 4531 if (! is_array($arr2)) { 4532 $arr2 = $arr; // copy the original array 4533 } 4534 while (count($arr) < $size) 4535 $arr = array_merge_php4($arr, $arr2); // append until done 4536} 4537 4538/*! 4539 * Fixes problem with array_merge() in PHP5. 4540 * \note I simply copied this from a bug report. I am not running php5 yet, so 4541 * I cannot reproduce it, which is why I trust the reporter. 4542 */ 4543function array_merge_php4($array1,$array2) 4544{ 4545 $return=array(); 4546 4547 foreach(func_get_args() as $arg){ 4548 if(!is_array($arg)){ 4549 $arg=array($arg); 4550 } 4551 foreach($arg as $key=>$val){ 4552 if(!is_int($key)){ 4553 $return[$key]=$val; 4554 }else{ 4555 $return[]=$val; 4556 } 4557 } 4558 } 4559 return $return; 4560 } 4561 4562 4563 4564 4565?> 4566