| [ Index ] |
PHP Cross Reference of MantisBT |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * File containing the ezcGraphLineChart class 4 * 5 * @package Graph 6 * @version 1.5 7 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. 8 * @license http://ez.no/licenses/new_bsd New BSD License 9 */ 10 /** 11 * Class for line charts. Can make use of an unlimited amount of datasets and 12 * will display them as lines by default. 13 * X axis: 14 * - Labeled axis 15 * - Centered axis label renderer 16 * Y axis: 17 * - Numeric axis 18 * - Exact axis label renderer 19 * 20 * <code> 21 * // Create a new line chart 22 * $chart = new ezcGraphLineChart(); 23 * 24 * // Add data to line chart 25 * $chart->data['sample dataset'] = new ezcGraphArrayDataSet( 26 * array( 27 * '100' => 1.2, 28 * '200' => 43.2, 29 * '300' => -34.14, 30 * '350' => 65, 31 * '400' => 123, 32 * ) 33 * ); 34 * 35 * // Render chart with default 2d renderer and default SVG driver 36 * $chart->render( 500, 200, 'line_chart.svg' ); 37 * </code> 38 * 39 * Each chart consists of several chart elements which represents logical 40 * parts of the chart and can be formatted independently. The line chart 41 * consists of: 42 * - title ( {@link ezcGraphChartElementText} ) 43 * - legend ( {@link ezcGraphChartElementLegend} ) 44 * - background ( {@link ezcGraphChartElementBackground} ) 45 * - xAxis ( {@link ezcGraphChartElementLabeledAxis} ) 46 * - yAxis ( {@link ezcGraphChartElementNumericAxis} ) 47 * 48 * The type of the axis may be changed and all elements can be configured by 49 * accessing them as properties of the chart: 50 * 51 * <code> 52 * $chart->legend->position = ezcGraph::RIGHT; 53 * </code> 54 * 55 * The chart itself also offers several options to configure the appearance. 56 * The extended configure options are available in 57 * {@link ezcGraphLineChartOptions} extending the {@link ezcGraphChartOptions}. 58 * 59 * @property ezcGraphLineChartOptions $options 60 * Chart options class 61 * 62 * @version 1.5 63 * @package Graph 64 * @mainclass 65 */ 66 class ezcGraphLineChart extends ezcGraphChart 67 { 68 /** 69 * Array with additional axis for the chart 70 * 71 * @var ezcGraphAxisContainer 72 */ 73 protected $additionalAxis; 74 75 /** 76 * Constructor 77 * 78 * @param array $options Default option array 79 * @return void 80 * @ignore 81 */ 82 public function __construct( array $options = array() ) 83 { 84 $this->additionalAxis = new ezcGraphAxisContainer( $this ); 85 86 $this->options = new ezcGraphLineChartOptions( $options ); 87 $this->options->highlightFont = $this->options->font; 88 89 parent::__construct(); 90 91 $this->addElement( 'xAxis', new ezcGraphChartElementLabeledAxis() ); 92 $this->elements['xAxis']->position = ezcGraph::LEFT; 93 94 $this->addElement( 'yAxis', new ezcGraphChartElementNumericAxis() ); 95 $this->elements['yAxis']->position = ezcGraph::BOTTOM; 96 } 97 98 /** 99 * __get 100 * 101 * @param mixed $propertyName 102 * @throws ezcBasePropertyNotFoundException 103 * If a the value for the property options is not an instance of 104 * @return mixed 105 * @ignore 106 */ 107 public function __get( $propertyName ) 108 { 109 switch ( $propertyName ) 110 { 111 case 'additionalAxis': 112 return $this->additionalAxis; 113 } 114 115 return parent::__get( $propertyName ); 116 } 117 118 /** 119 * Options write access 120 * 121 * @throws ezcBasePropertyNotFoundException 122 * If Option could not be found 123 * @throws ezcBaseValueException 124 * If value is out of range 125 * @param mixed $propertyName Option name 126 * @param mixed $propertyValue Option value; 127 * @return mixed 128 * @ignore 129 */ 130 public function __set( $propertyName, $propertyValue ) 131 { 132 switch ( $propertyName ) { 133 case 'xAxis': 134 if ( $propertyValue instanceof ezcGraphChartElementAxis ) 135 { 136 $this->addElement( 'xAxis', $propertyValue ); 137 $this->elements['xAxis']->position = ezcGraph::LEFT; 138 } 139 else 140 { 141 throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); 142 } 143 break; 144 case 'yAxis': 145 if ( $propertyValue instanceof ezcGraphChartElementAxis ) 146 { 147 $this->addElement( 'yAxis', $propertyValue ); 148 $this->elements['yAxis']->position = ezcGraph::BOTTOM; 149 } 150 else 151 { 152 throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); 153 } 154 break; 155 default: 156 parent::__set( $propertyName, $propertyValue ); 157 } 158 } 159 160 /** 161 * Set colors and border for this element 162 * 163 * @param ezcGraphPalette $palette Palette 164 * @return void 165 */ 166 public function setFromPalette( ezcGraphPalette $palette ) 167 { 168 foreach ( $this->additionalAxis as $element ) 169 { 170 $element->setFromPalette( $palette ); 171 } 172 173 parent::setFromPalette( $palette ); 174 } 175 176 /** 177 * Calculate bar chart step width 178 * 179 * @return void 180 */ 181 protected function calculateStepWidth( ezcGraphChartElementAxis $mainAxis, ezcGraphChartElementAxis $secondAxis, $width ) 182 { 183 $steps = $mainAxis->getSteps(); 184 185 $stepWidth = null; 186 foreach ( $steps as $step ) 187 { 188 if ( $stepWidth === null ) 189 { 190 $stepWidth = $step->width; 191 } 192 elseif ( $step->width !== $stepWidth ) 193 { 194 throw new ezcGraphUnregularStepsException(); 195 } 196 } 197 198 $step = reset( $steps ); 199 if ( count( $step->childs ) ) 200 { 201 // Keep this for BC reasons 202 $barCount = ( $mainAxis->getMajorStepCount() + 1 ) * ( $mainAxis->getMinorStepCount() - 1 ); 203 $stepWidth = 1 / $barCount; 204 } 205 206 $checkedRegularSteps = true; 207 return $mainAxis->axisLabelRenderer->modifyChartDataPosition( 208 $secondAxis->axisLabelRenderer->modifyChartDataPosition( 209 new ezcGraphCoordinate( 210 $width * $stepWidth, 211 $width * $stepWidth 212 ) 213 ) 214 ); 215 } 216 217 /** 218 * Render the assigned data 219 * 220 * Will renderer all charts data in the remaining boundings after drawing 221 * all other chart elements. The data will be rendered depending on the 222 * settings in the dataset. 223 * 224 * @param ezcGraphRenderer $renderer Renderer 225 * @param ezcGraphBoundings $boundings Remaining boundings 226 * @return void 227 */ 228 protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphBoundings $innerBoundings ) 229 { 230 // Use inner boundings for drawning chart data 231 $boundings = $innerBoundings; 232 233 $yAxisNullPosition = $this->elements['yAxis']->getCoordinate( false ); 234 235 // Initialize counters 236 $nr = array(); 237 $count = array(); 238 239 foreach ( $this->data as $data ) 240 { 241 if ( !isset( $nr[$data->displayType->default] ) ) 242 { 243 $nr[$data->displayType->default] = 0; 244 $count[$data->displayType->default] = 0; 245 } 246 247 $nr[$data->displayType->default]++; 248 $count[$data->displayType->default]++; 249 } 250 251 $checkedRegularSteps = false; 252 253 // Display data 254 foreach ( $this->data as $datasetName => $data ) 255 { 256 --$nr[$data->displayType->default]; 257 258 // Check which axis should be used 259 $xAxis = ( $data->xAxis->default ? $data->xAxis->default: $this->elements['xAxis'] ); 260 $yAxis = ( $data->yAxis->default ? $data->yAxis->default: $this->elements['yAxis'] ); 261 262 // Determine fill color for dataset 263 if ( $this->options->fillLines !== false ) 264 { 265 $fillColor = clone $data->color->default; 266 $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) ); 267 } 268 else 269 { 270 $fillColor = null; 271 } 272 273 // Ensure regular steps on axis when used with bar charts and 274 // precalculate some values use to render bar charts 275 // 276 // Called only once and only when bars should be rendered 277 if ( ( $checkedRegularSteps === false ) && 278 ( $data->displayType->default === ezcGraph::BAR ) ) 279 { 280 $width = $this->calculateStepWidth( $xAxis, $yAxis, $boundings->width )->x; 281 } 282 283 // Draw lines for dataset 284 $lastPoint = false; 285 foreach ( $data as $key => $value ) 286 { 287 // Calculate point in chart 288 $point = $xAxis->axisLabelRenderer->modifyChartDataPosition( 289 $yAxis->axisLabelRenderer->modifyChartDataPosition( 290 new ezcGraphCoordinate( 291 $xAxis->getCoordinate( $key ), 292 $yAxis->getCoordinate( $value ) 293 ) 294 ) 295 ); 296 297 // Render depending on display type of dataset 298 switch ( true ) 299 { 300 case $data->displayType->default === ezcGraph::LINE: 301 $renderer->drawDataLine( 302 $boundings, 303 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 304 $data->color->default, 305 ( $lastPoint === false ? $point : $lastPoint ), 306 $point, 307 $nr[$data->displayType->default], 308 $count[$data->displayType->default], 309 $data->symbol[$key], 310 $data->color[$key], 311 $fillColor, 312 $yAxisNullPosition, 313 ( $data->lineThickness->default ? $data->lineThickness->default : $this->options->lineThickness ) 314 ); 315 316 // Render highlight string if requested 317 if ( $data->highlight[$key] ) 318 { 319 $renderer->drawDataHighlightText( 320 $boundings, 321 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 322 $point, 323 $yAxisNullPosition, 324 $nr[$data->displayType->default], 325 $count[$data->displayType->default], 326 $this->options->highlightFont, 327 ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), 328 $this->options->highlightSize + $this->options->highlightFont->padding * 2, 329 ( $this->options->highlightLines ? $data->color[$key] : null ), 330 ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), 331 ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), 332 0., 333 ezcGraph::LINE 334 ); 335 } 336 break; 337 case ( $data->displayType->default === ezcGraph::BAR ) && 338 $this->options->stackBars : 339 // Check if a bar has already been stacked 340 if ( !isset( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) ) 341 { 342 $start = new ezcGraphCoordinate( 343 $point->x, 344 $yAxisNullPosition 345 ); 346 347 $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] = $value; 348 } 349 else 350 { 351 $start = $xAxis->axisLabelRenderer->modifyChartDataPosition( 352 $yAxis->axisLabelRenderer->modifyChartDataPosition( 353 new ezcGraphCoordinate( 354 $xAxis->getCoordinate( $key ), 355 $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) 356 ) 357 ) 358 ); 359 360 $point = $xAxis->axisLabelRenderer->modifyChartDataPosition( 361 $yAxis->axisLabelRenderer->modifyChartDataPosition( 362 new ezcGraphCoordinate( 363 $xAxis->getCoordinate( $key ), 364 $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] += $value ) 365 ) 366 ) 367 ); 368 } 369 370 // Force one symbol for each stacked bar 371 if ( !isset( $stackedSymbol[(int) ( $point->x * 10000 )] ) ) 372 { 373 $stackedSymbol[(int) ( $point->x * 10000 )] = $data->symbol[$key]; 374 } 375 376 // Store stacked value for next iteration 377 $side = ( $point->y == 0 ? 1 : $point->y / abs( $point->y ) ); 378 $stacked[(int) ( $point->x * 10000 )][$side] = $point; 379 380 $renderer->drawStackedBar( 381 $boundings, 382 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 383 $data->color->default, 384 $start, 385 $point, 386 $width, 387 $stackedSymbol[(int) ( $point->x * 10000 )], 388 $yAxisNullPosition 389 ); 390 391 // Render highlight string if requested 392 if ( $data->highlight[$key] ) 393 { 394 $renderer->drawDataHighlightText( 395 $boundings, 396 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 397 $point, 398 $yAxisNullPosition, 399 $nr[$data->displayType->default], 400 $count[$data->displayType->default], 401 $this->options->highlightFont, 402 ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), 403 $this->options->highlightSize + $this->options->highlightFont->padding * 2, 404 ( $this->options->highlightLines ? $data->color[$key] : null ), 405 ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), 406 ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), 407 0., 408 ezcGraph::LINE 409 ); 410 } 411 break; 412 case $data->displayType->default === ezcGraph::BAR: 413 $renderer->drawBar( 414 $boundings, 415 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 416 $data->color[$key], 417 $point, 418 $width, 419 $nr[$data->displayType->default], 420 $count[$data->displayType->default], 421 $data->symbol[$key], 422 $yAxisNullPosition 423 ); 424 425 // Render highlight string if requested 426 if ( $data->highlight[$key] ) 427 { 428 $renderer->drawDataHighlightText( 429 $boundings, 430 new ezcGraphContext( $datasetName, $key, $data->url[$key] ), 431 $point, 432 $yAxisNullPosition, 433 $nr[$data->displayType->default], 434 $count[$data->displayType->default], 435 $this->options->highlightFont, 436 ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), 437 $this->options->highlightSize + $this->options->highlightFont->padding * 2, 438 ( $this->options->highlightLines ? $data->color[$key] : null ), 439 ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), 440 ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), 441 $width, 442 $data->displayType->default 443 ); 444 } 445 break; 446 default: 447 throw new ezcGraphInvalidDisplayTypeException( $data->displayType->default ); 448 break; 449 } 450 451 // Store last point, used to connect lines in line chart. 452 $lastPoint = $point; 453 } 454 } 455 } 456 457 /** 458 * Returns the default display type of the current chart type. 459 * 460 * @return int Display type 461 */ 462 public function getDefaultDisplayType() 463 { 464 return ezcGraph::LINE; 465 } 466 467 /** 468 * Check if renderer supports features requested by some special chart 469 * options. 470 * 471 * @throws ezcBaseValueException 472 * If some feature is not supported 473 * 474 * @return void 475 */ 476 protected function checkRenderer() 477 { 478 // When stacked bars are enabled, check if renderer supports them 479 if ( $this->options->stackBars ) 480 { 481 if ( !$this->renderer instanceof ezcGraphStackedBarsRenderer ) 482 { 483 throw new ezcBaseValueException( 'renderer', $this->renderer, 'ezcGraphStackedBarsRenderer' ); 484 } 485 } 486 } 487 488 /** 489 * Aggregate and calculate value boundings on axis. 490 * 491 * @return void 492 */ 493 protected function setAxisValues() 494 { 495 // Virtual data set build for agrregated values sums for bar charts 496 $virtualBarSumDataSet = array( array(), array() ); 497 498 // Calculate axis scaling and labeling 499 foreach ( $this->data as $dataset ) 500 { 501 $nr = 0; 502 $labels = array(); 503 $values = array(); 504 foreach ( $dataset as $label => $value ) 505 { 506 $labels[] = $label; 507 $values[] = $value; 508 509 // Build sum of all bars 510 if ( $this->options->stackBars && 511 ( $dataset->displayType->default === ezcGraph::BAR ) ) 512 { 513 if ( !isset( $virtualBarSumDataSet[(int) $value >= 0][$nr] ) ) 514 { 515 $virtualBarSumDataSet[(int) $value >= 0][$nr++] = $value; 516 } 517 else 518 { 519 $virtualBarSumDataSet[(int) $value >= 0][$nr++] += $value; 520 } 521 } 522 } 523 524 // Check if data has been associated with another custom axis, use 525 // default axis otherwise. 526 if ( $dataset->xAxis->default ) 527 { 528 $dataset->xAxis->default->addData( $labels ); 529 } 530 else 531 { 532 $this->elements['xAxis']->addData( $labels ); 533 } 534 535 if ( $dataset->yAxis->default ) 536 { 537 $dataset->yAxis->default->addData( $values ); 538 } 539 else 540 { 541 $this->elements['yAxis']->addData( $values ); 542 } 543 } 544 545 // Also use stacked bar values as base for y axis value span 546 // calculation 547 if ( $this->options->stackBars ) 548 { 549 $this->elements['yAxis']->addData( $virtualBarSumDataSet[0] ); 550 $this->elements['yAxis']->addData( $virtualBarSumDataSet[1] ); 551 } 552 553 // There should always be something assigned to the main x and y axis. 554 if ( !$this->elements['xAxis']->initialized || 555 !$this->elements['yAxis']->initialized ) 556 { 557 throw new ezcGraphNoDataException(); 558 } 559 560 // Calculate boundings from assigned data 561 $this->elements['xAxis']->calculateAxisBoundings(); 562 $this->elements['yAxis']->calculateAxisBoundings(); 563 } 564 565 /** 566 * Renders the basic elements of this chart type 567 * 568 * @param int $width 569 * @param int $height 570 * @return void 571 */ 572 protected function renderElements( $width, $height ) 573 { 574 if ( !count( $this->data ) ) 575 { 576 throw new ezcGraphNoDataException(); 577 } 578 579 // Check if renderer supports requested features 580 $this->checkRenderer(); 581 582 // Set values form datasets on axis to calculate correct spans 583 $this->setAxisValues(); 584 585 // Generate legend 586 $this->elements['legend']->generateFromDataSets( $this->data ); 587 588 // Get boundings from parameters 589 $this->options->width = $width; 590 $this->options->height = $height; 591 592 // Set image properties in driver 593 $this->driver->options->width = $width; 594 $this->driver->options->height = $height; 595 596 // Render subelements 597 $boundings = new ezcGraphBoundings(); 598 $boundings->x1 = $this->options->width; 599 $boundings->y1 = $this->options->height; 600 601 $boundings = $this->elements['xAxis']->axisLabelRenderer->modifyChartBoundings( 602 $this->elements['yAxis']->axisLabelRenderer->modifyChartBoundings( 603 $boundings, new ezcGraphCoordinate( 1, 0 ) 604 ), new ezcGraphCoordinate( -1, 0 ) 605 ); 606 607 // Render subelements 608 foreach ( $this->elements as $name => $element ) 609 { 610 // Skip element, if it should not get rendered 611 if ( ( $this->renderElement[$name] === false ) || 612 ( $name === 'xAxis' ) || 613 ( $name === 'yAxis' ) ) 614 { 615 continue; 616 } 617 618 $this->driver->options->font = $element->font; 619 $boundings = $element->render( $this->renderer, $boundings ); 620 } 621 622 // Set relative positions of axis in chart depending on the "null" 623 // value on the other axis. 624 $this->elements['xAxis']->nullPosition = $this->elements['yAxis']->getCoordinate( false ); 625 $this->elements['yAxis']->nullPosition = $this->elements['xAxis']->getCoordinate( false ); 626 627 // Calculate inner data boundings of chart 628 $innerBoundings = new ezcGraphBoundings( 629 $boundings->x0 + $boundings->width * 630 $this->elements['yAxis']->axisSpace, 631 $boundings->y0 + $boundings->height * 632 ( ( $this->elements['xAxis']->outerAxisSpace === null ) ? 633 $this->elements['xAxis']->axisSpace : 634 $this->elements['xAxis']->outerAxisSpace ), 635 $boundings->x1 - $boundings->width * 636 ( ( $this->elements['yAxis']->outerAxisSpace === null ) ? 637 $this->elements['yAxis']->axisSpace : 638 $this->elements['yAxis']->outerAxisSpace ), 639 $boundings->y1 - $boundings->height * 640 $this->elements['xAxis']->axisSpace 641 ); 642 643 // Render axis 644 $this->driver->options->font = $this->elements['yAxis']->font; 645 $boundings = $this->elements['xAxis']->render( $this->renderer, $boundings, $innerBoundings ); 646 $boundings = $this->elements['yAxis']->render( $this->renderer, $boundings, $innerBoundings ); 647 648 // Render additional axis 649 foreach ( $this->additionalAxis as $element ) 650 { 651 if ( $element->initialized ) 652 { 653 // Calculate all required step sizes if values has been 654 // assigned to axis. 655 $element->calculateAxisBoundings(); 656 } 657 else 658 { 659 // Do not render any axis labels, if no values were assigned 660 // and no step sizes were defined. 661 $element->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer(); 662 } 663 664 $this->driver->options->font = $element->font; 665 $element->nullPosition = $element->chartPosition; 666 $boundings = $element->render( $this->renderer, $boundings, $innerBoundings ); 667 } 668 669 // Render graph 670 $this->renderData( $this->renderer, $boundings, $innerBoundings ); 671 } 672 673 /** 674 * Render the line chart 675 * 676 * Renders the chart into a file or stream. The width and height are 677 * needed to specify the dimensions of the resulting image. For direct 678 * output use 'php://stdout' as output file. 679 * 680 * @param int $width Image width 681 * @param int $height Image height 682 * @param string $file Output file 683 * @apichange 684 * @return void 685 */ 686 public function render( $width, $height, $file = null ) 687 { 688 $this->renderElements( $width, $height ); 689 690 if ( !empty( $file ) ) 691 { 692 $this->renderer->render( $file ); 693 } 694 695 $this->renderedFile = $file; 696 } 697 698 /** 699 * Renders this chart to direct output 700 * 701 * Does the same as ezcGraphChart::render(), but renders directly to 702 * output and not into a file. 703 * 704 * @param int $width 705 * @param int $height 706 * @apichange 707 * @return void 708 */ 709 public function renderToOutput( $width, $height ) 710 { 711 // @TODO: merge this function with render an deprecate ommit of third 712 // argument in render() when API break is possible 713 $this->renderElements( $width, $height ); 714 $this->renderer->render( null ); 715 } 716 } 717 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Jul 28 15:48:31 2011 | Cross-referenced by PHPXref 0.7 |