* // 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' ); * * * 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: * * * $chart->legend->position = ezcGraph::RIGHT; * * * 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 //autogentag// * @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 ( $data->fillLine !== false ) { $fillColor = clone $data->color->default; $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $data->fillLine / 255 ) ); } else 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 ); } } ?>