/MantisBT/library/ezc/Graph/src/charts/line.php
PHP | 717 lines | 433 code | 65 blank | 219 comment | 28 complexity | 64bf2c07ad90db5b65a2ceb0c8292db2 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, LGPL-2.1, GPL-3.0, LGPL-2.0, AGPL-3.0
- <?php
- /**
- * File containing the ezcGraphLineChart class
- *
- * @package Graph
- * @version 1.5
- * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
- * @license http://ez.no/licenses/new_bsd New BSD License
- */
- /**
- * Class for line charts. Can make use of an unlimited amount of datasets and
- * will display them as lines by default.
- * X axis:
- * - Labeled axis
- * - Centered axis label renderer
- * Y axis:
- * - Numeric axis
- * - Exact axis label renderer
- *
- * <code>
- * // Create a new line chart
- * $chart = new ezcGraphLineChart();
- *
- * // Add data to line chart
- * $chart->data['sample dataset'] = new ezcGraphArrayDataSet(
- * array(
- * '100' => 1.2,
- * '200' => 43.2,
- * '300' => -34.14,
- * '350' => 65,
- * '400' => 123,
- * )
- * );
- *
- * // Render chart with default 2d renderer and default SVG driver
- * $chart->render( 500, 200, 'line_chart.svg' );
- * </code>
- *
- * Each chart consists of several chart elements which represents logical
- * parts of the chart and can be formatted independently. The line chart
- * consists of:
- * - title ( {@link ezcGraphChartElementText} )
- * - legend ( {@link ezcGraphChartElementLegend} )
- * - background ( {@link ezcGraphChartElementBackground} )
- * - xAxis ( {@link ezcGraphChartElementLabeledAxis} )
- * - yAxis ( {@link ezcGraphChartElementNumericAxis} )
- *
- * The type of the axis may be changed and all elements can be configured by
- * accessing them as properties of the chart:
- *
- * <code>
- * $chart->legend->position = ezcGraph::RIGHT;
- * </code>
- *
- * The chart itself also offers several options to configure the appearance.
- * The extended configure options are available in
- * {@link ezcGraphLineChartOptions} extending the {@link ezcGraphChartOptions}.
- *
- * @property ezcGraphLineChartOptions $options
- * Chart options class
- *
- * @version 1.5
- * @package Graph
- * @mainclass
- */
- class ezcGraphLineChart extends ezcGraphChart
- {
- /**
- * Array with additional axis for the chart
- *
- * @var ezcGraphAxisContainer
- */
- protected $additionalAxis;
- /**
- * Constructor
- *
- * @param array $options Default option array
- * @return void
- * @ignore
- */
- public function __construct( array $options = array() )
- {
- $this->additionalAxis = new ezcGraphAxisContainer( $this );
- $this->options = new ezcGraphLineChartOptions( $options );
- $this->options->highlightFont = $this->options->font;
- parent::__construct();
- $this->addElement( 'xAxis', new ezcGraphChartElementLabeledAxis() );
- $this->elements['xAxis']->position = ezcGraph::LEFT;
- $this->addElement( 'yAxis', new ezcGraphChartElementNumericAxis() );
- $this->elements['yAxis']->position = ezcGraph::BOTTOM;
- }
- /**
- * __get
- *
- * @param mixed $propertyName
- * @throws ezcBasePropertyNotFoundException
- * If a the value for the property options is not an instance of
- * @return mixed
- * @ignore
- */
- public function __get( $propertyName )
- {
- switch ( $propertyName )
- {
- case 'additionalAxis':
- return $this->additionalAxis;
- }
- return parent::__get( $propertyName );
- }
- /**
- * Options write access
- *
- * @throws ezcBasePropertyNotFoundException
- * If Option could not be found
- * @throws ezcBaseValueException
- * If value is out of range
- * @param mixed $propertyName Option name
- * @param mixed $propertyValue Option value;
- * @return mixed
- * @ignore
- */
- public function __set( $propertyName, $propertyValue )
- {
- switch ( $propertyName ) {
- case 'xAxis':
- if ( $propertyValue instanceof ezcGraphChartElementAxis )
- {
- $this->addElement( 'xAxis', $propertyValue );
- $this->elements['xAxis']->position = ezcGraph::LEFT;
- }
- else
- {
- throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
- }
- break;
- case 'yAxis':
- if ( $propertyValue instanceof ezcGraphChartElementAxis )
- {
- $this->addElement( 'yAxis', $propertyValue );
- $this->elements['yAxis']->position = ezcGraph::BOTTOM;
- }
- else
- {
- throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
- }
- break;
- default:
- parent::__set( $propertyName, $propertyValue );
- }
- }
- /**
- * Set colors and border for this element
- *
- * @param ezcGraphPalette $palette Palette
- * @return void
- */
- public function setFromPalette( ezcGraphPalette $palette )
- {
- foreach ( $this->additionalAxis as $element )
- {
- $element->setFromPalette( $palette );
- }
- parent::setFromPalette( $palette );
- }
- /**
- * Calculate bar chart step width
- *
- * @return void
- */
- protected function calculateStepWidth( ezcGraphChartElementAxis $mainAxis, ezcGraphChartElementAxis $secondAxis, $width )
- {
- $steps = $mainAxis->getSteps();
- $stepWidth = null;
- foreach ( $steps as $step )
- {
- if ( $stepWidth === null )
- {
- $stepWidth = $step->width;
- }
- elseif ( $step->width !== $stepWidth )
- {
- throw new ezcGraphUnregularStepsException();
- }
- }
- $step = reset( $steps );
- if ( count( $step->childs ) )
- {
- // Keep this for BC reasons
- $barCount = ( $mainAxis->getMajorStepCount() + 1 ) * ( $mainAxis->getMinorStepCount() - 1 );
- $stepWidth = 1 / $barCount;
- }
- $checkedRegularSteps = true;
- return $mainAxis->axisLabelRenderer->modifyChartDataPosition(
- $secondAxis->axisLabelRenderer->modifyChartDataPosition(
- new ezcGraphCoordinate(
- $width * $stepWidth,
- $width * $stepWidth
- )
- )
- );
- }
- /**
- * Render the assigned data
- *
- * Will renderer all charts data in the remaining boundings after drawing
- * all other chart elements. The data will be rendered depending on the
- * settings in the dataset.
- *
- * @param ezcGraphRenderer $renderer Renderer
- * @param ezcGraphBoundings $boundings Remaining boundings
- * @return void
- */
- protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphBoundings $innerBoundings )
- {
- // Use inner boundings for drawning chart data
- $boundings = $innerBoundings;
- $yAxisNullPosition = $this->elements['yAxis']->getCoordinate( false );
- // Initialize counters
- $nr = array();
- $count = array();
- foreach ( $this->data as $data )
- {
- if ( !isset( $nr[$data->displayType->default] ) )
- {
- $nr[$data->displayType->default] = 0;
- $count[$data->displayType->default] = 0;
- }
- $nr[$data->displayType->default]++;
- $count[$data->displayType->default]++;
- }
- $checkedRegularSteps = false;
- // Display data
- foreach ( $this->data as $datasetName => $data )
- {
- --$nr[$data->displayType->default];
- // Check which axis should be used
- $xAxis = ( $data->xAxis->default ? $data->xAxis->default: $this->elements['xAxis'] );
- $yAxis = ( $data->yAxis->default ? $data->yAxis->default: $this->elements['yAxis'] );
- // Determine fill color for dataset
- if ( $this->options->fillLines !== false )
- {
- $fillColor = clone $data->color->default;
- $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) );
- }
- else
- {
- $fillColor = null;
- }
- // Ensure regular steps on axis when used with bar charts and
- // precalculate some values use to render bar charts
- //
- // Called only once and only when bars should be rendered
- if ( ( $checkedRegularSteps === false ) &&
- ( $data->displayType->default === ezcGraph::BAR ) )
- {
- $width = $this->calculateStepWidth( $xAxis, $yAxis, $boundings->width )->x;
- }
- // Draw lines for dataset
- $lastPoint = false;
- foreach ( $data as $key => $value )
- {
- // Calculate point in chart
- $point = $xAxis->axisLabelRenderer->modifyChartDataPosition(
- $yAxis->axisLabelRenderer->modifyChartDataPosition(
- new ezcGraphCoordinate(
- $xAxis->getCoordinate( $key ),
- $yAxis->getCoordinate( $value )
- )
- )
- );
- // Render depending on display type of dataset
- switch ( true )
- {
- case $data->displayType->default === ezcGraph::LINE:
- $renderer->drawDataLine(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $data->color->default,
- ( $lastPoint === false ? $point : $lastPoint ),
- $point,
- $nr[$data->displayType->default],
- $count[$data->displayType->default],
- $data->symbol[$key],
- $data->color[$key],
- $fillColor,
- $yAxisNullPosition,
- ( $data->lineThickness->default ? $data->lineThickness->default : $this->options->lineThickness )
- );
- // Render highlight string if requested
- if ( $data->highlight[$key] )
- {
- $renderer->drawDataHighlightText(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $point,
- $yAxisNullPosition,
- $nr[$data->displayType->default],
- $count[$data->displayType->default],
- $this->options->highlightFont,
- ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ),
- $this->options->highlightSize + $this->options->highlightFont->padding * 2,
- ( $this->options->highlightLines ? $data->color[$key] : null ),
- ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ),
- ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ),
- 0.,
- ezcGraph::LINE
- );
- }
- break;
- case ( $data->displayType->default === ezcGraph::BAR ) &&
- $this->options->stackBars :
- // Check if a bar has already been stacked
- if ( !isset( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) )
- {
- $start = new ezcGraphCoordinate(
- $point->x,
- $yAxisNullPosition
- );
- $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] = $value;
- }
- else
- {
- $start = $xAxis->axisLabelRenderer->modifyChartDataPosition(
- $yAxis->axisLabelRenderer->modifyChartDataPosition(
- new ezcGraphCoordinate(
- $xAxis->getCoordinate( $key ),
- $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] )
- )
- )
- );
- $point = $xAxis->axisLabelRenderer->modifyChartDataPosition(
- $yAxis->axisLabelRenderer->modifyChartDataPosition(
- new ezcGraphCoordinate(
- $xAxis->getCoordinate( $key ),
- $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] += $value )
- )
- )
- );
- }
- // Force one symbol for each stacked bar
- if ( !isset( $stackedSymbol[(int) ( $point->x * 10000 )] ) )
- {
- $stackedSymbol[(int) ( $point->x * 10000 )] = $data->symbol[$key];
- }
- // Store stacked value for next iteration
- $side = ( $point->y == 0 ? 1 : $point->y / abs( $point->y ) );
- $stacked[(int) ( $point->x * 10000 )][$side] = $point;
- $renderer->drawStackedBar(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $data->color->default,
- $start,
- $point,
- $width,
- $stackedSymbol[(int) ( $point->x * 10000 )],
- $yAxisNullPosition
- );
- // Render highlight string if requested
- if ( $data->highlight[$key] )
- {
- $renderer->drawDataHighlightText(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $point,
- $yAxisNullPosition,
- $nr[$data->displayType->default],
- $count[$data->displayType->default],
- $this->options->highlightFont,
- ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ),
- $this->options->highlightSize + $this->options->highlightFont->padding * 2,
- ( $this->options->highlightLines ? $data->color[$key] : null ),
- ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ),
- ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ),
- 0.,
- ezcGraph::LINE
- );
- }
- break;
- case $data->displayType->default === ezcGraph::BAR:
- $renderer->drawBar(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $data->color[$key],
- $point,
- $width,
- $nr[$data->displayType->default],
- $count[$data->displayType->default],
- $data->symbol[$key],
- $yAxisNullPosition
- );
- // Render highlight string if requested
- if ( $data->highlight[$key] )
- {
- $renderer->drawDataHighlightText(
- $boundings,
- new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
- $point,
- $yAxisNullPosition,
- $nr[$data->displayType->default],
- $count[$data->displayType->default],
- $this->options->highlightFont,
- ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ),
- $this->options->highlightSize + $this->options->highlightFont->padding * 2,
- ( $this->options->highlightLines ? $data->color[$key] : null ),
- ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ),
- ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ),
- $width,
- $data->displayType->default
- );
- }
- break;
- default:
- throw new ezcGraphInvalidDisplayTypeException( $data->displayType->default );
- break;
- }
-
- // Store last point, used to connect lines in line chart.
- $lastPoint = $point;
- }
- }
- }
- /**
- * Returns the default display type of the current chart type.
- *
- * @return int Display type
- */
- public function getDefaultDisplayType()
- {
- return ezcGraph::LINE;
- }
- /**
- * Check if renderer supports features requested by some special chart
- * options.
- *
- * @throws ezcBaseValueException
- * If some feature is not supported
- *
- * @return void
- */
- protected function checkRenderer()
- {
- // When stacked bars are enabled, check if renderer supports them
- if ( $this->options->stackBars )
- {
- if ( !$this->renderer instanceof ezcGraphStackedBarsRenderer )
- {
- throw new ezcBaseValueException( 'renderer', $this->renderer, 'ezcGraphStackedBarsRenderer' );
- }
- }
- }
- /**
- * Aggregate and calculate value boundings on axis.
- *
- * @return void
- */
- protected function setAxisValues()
- {
- // Virtual data set build for agrregated values sums for bar charts
- $virtualBarSumDataSet = array( array(), array() );
- // Calculate axis scaling and labeling
- foreach ( $this->data as $dataset )
- {
- $nr = 0;
- $labels = array();
- $values = array();
- foreach ( $dataset as $label => $value )
- {
- $labels[] = $label;
- $values[] = $value;
- // Build sum of all bars
- if ( $this->options->stackBars &&
- ( $dataset->displayType->default === ezcGraph::BAR ) )
- {
- if ( !isset( $virtualBarSumDataSet[(int) $value >= 0][$nr] ) )
- {
- $virtualBarSumDataSet[(int) $value >= 0][$nr++] = $value;
- }
- else
- {
- $virtualBarSumDataSet[(int) $value >= 0][$nr++] += $value;
- }
- }
- }
- // Check if data has been associated with another custom axis, use
- // default axis otherwise.
- if ( $dataset->xAxis->default )
- {
- $dataset->xAxis->default->addData( $labels );
- }
- else
- {
- $this->elements['xAxis']->addData( $labels );
- }
- if ( $dataset->yAxis->default )
- {
- $dataset->yAxis->default->addData( $values );
- }
- else
- {
- $this->elements['yAxis']->addData( $values );
- }
- }
- // Also use stacked bar values as base for y axis value span
- // calculation
- if ( $this->options->stackBars )
- {
- $this->elements['yAxis']->addData( $virtualBarSumDataSet[0] );
- $this->elements['yAxis']->addData( $virtualBarSumDataSet[1] );
- }
- // There should always be something assigned to the main x and y axis.
- if ( !$this->elements['xAxis']->initialized ||
- !$this->elements['yAxis']->initialized )
- {
- throw new ezcGraphNoDataException();
- }
- // Calculate boundings from assigned data
- $this->elements['xAxis']->calculateAxisBoundings();
- $this->elements['yAxis']->calculateAxisBoundings();
- }
- /**
- * Renders the basic elements of this chart type
- *
- * @param int $width
- * @param int $height
- * @return void
- */
- protected function renderElements( $width, $height )
- {
- if ( !count( $this->data ) )
- {
- throw new ezcGraphNoDataException();
- }
- // Check if renderer supports requested features
- $this->checkRenderer();
- // Set values form datasets on axis to calculate correct spans
- $this->setAxisValues();
- // Generate legend
- $this->elements['legend']->generateFromDataSets( $this->data );
- // Get boundings from parameters
- $this->options->width = $width;
- $this->options->height = $height;
- // Set image properties in driver
- $this->driver->options->width = $width;
- $this->driver->options->height = $height;
- // Render subelements
- $boundings = new ezcGraphBoundings();
- $boundings->x1 = $this->options->width;
- $boundings->y1 = $this->options->height;
- $boundings = $this->elements['xAxis']->axisLabelRenderer->modifyChartBoundings(
- $this->elements['yAxis']->axisLabelRenderer->modifyChartBoundings(
- $boundings, new ezcGraphCoordinate( 1, 0 )
- ), new ezcGraphCoordinate( -1, 0 )
- );
- // Render subelements
- foreach ( $this->elements as $name => $element )
- {
- // Skip element, if it should not get rendered
- if ( ( $this->renderElement[$name] === false ) ||
- ( $name === 'xAxis' ) ||
- ( $name === 'yAxis' ) )
- {
- continue;
- }
- $this->driver->options->font = $element->font;
- $boundings = $element->render( $this->renderer, $boundings );
- }
- // Set relative positions of axis in chart depending on the "null"
- // value on the other axis.
- $this->elements['xAxis']->nullPosition = $this->elements['yAxis']->getCoordinate( false );
- $this->elements['yAxis']->nullPosition = $this->elements['xAxis']->getCoordinate( false );
- // Calculate inner data boundings of chart
- $innerBoundings = new ezcGraphBoundings(
- $boundings->x0 + $boundings->width *
- $this->elements['yAxis']->axisSpace,
- $boundings->y0 + $boundings->height *
- ( ( $this->elements['xAxis']->outerAxisSpace === null ) ?
- $this->elements['xAxis']->axisSpace :
- $this->elements['xAxis']->outerAxisSpace ),
- $boundings->x1 - $boundings->width *
- ( ( $this->elements['yAxis']->outerAxisSpace === null ) ?
- $this->elements['yAxis']->axisSpace :
- $this->elements['yAxis']->outerAxisSpace ),
- $boundings->y1 - $boundings->height *
- $this->elements['xAxis']->axisSpace
- );
- // Render axis
- $this->driver->options->font = $this->elements['yAxis']->font;
- $boundings = $this->elements['xAxis']->render( $this->renderer, $boundings, $innerBoundings );
- $boundings = $this->elements['yAxis']->render( $this->renderer, $boundings, $innerBoundings );
- // Render additional axis
- foreach ( $this->additionalAxis as $element )
- {
- if ( $element->initialized )
- {
- // Calculate all required step sizes if values has been
- // assigned to axis.
- $element->calculateAxisBoundings();
- }
- else
- {
- // Do not render any axis labels, if no values were assigned
- // and no step sizes were defined.
- $element->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer();
- }
- $this->driver->options->font = $element->font;
- $element->nullPosition = $element->chartPosition;
- $boundings = $element->render( $this->renderer, $boundings, $innerBoundings );
- }
- // Render graph
- $this->renderData( $this->renderer, $boundings, $innerBoundings );
- }
- /**
- * Render the line chart
- *
- * Renders the chart into a file or stream. The width and height are
- * needed to specify the dimensions of the resulting image. For direct
- * output use 'php://stdout' as output file.
- *
- * @param int $width Image width
- * @param int $height Image height
- * @param string $file Output file
- * @apichange
- * @return void
- */
- public function render( $width, $height, $file = null )
- {
- $this->renderElements( $width, $height );
- if ( !empty( $file ) )
- {
- $this->renderer->render( $file );
- }
- $this->renderedFile = $file;
- }
- /**
- * Renders this chart to direct output
- *
- * Does the same as ezcGraphChart::render(), but renders directly to
- * output and not into a file.
- *
- * @param int $width
- * @param int $height
- * @apichange
- * @return void
- */
- public function renderToOutput( $width, $height )
- {
- // @TODO: merge this function with render an deprecate ommit of third
- // argument in render() when API break is possible
- $this->renderElements( $width, $height );
- $this->renderer->render( null );
- }
- }
- ?>