blob: d37b3ce346f66204d3732164f47f86133039d394 [file] [log] [blame]
<?php
/**
* File containing the ezcGraphAxisRotatedLabelRenderer class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Graph
* @version //autogentag//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Can render axis labels rotated, so that more axis labels fit on one axis.
* Produces best results if the axis space was increased, so that more spcae is
* available below the axis.
*
* <code>
* $chart->xAxis->axisLabelRenderer = new ezcGraphAxisRotatedLabelRenderer();
*
* // Define angle manually in degree
* $chart->xAxis->axisLabelRenderer->angle = 45;
*
* // Increase axis space
* $chart->xAxis->axisSpace = .2;
* </code>
*
* @property float $angle
* Angle of labels on axis in degrees.
*
* @version //autogentag//
* @package Graph
* @mainclass
*/
class ezcGraphAxisRotatedLabelRenderer extends ezcGraphAxisLabelRenderer
{
/**
* Store step array for later coordinate modifications
*
* @var array(ezcGraphStep)
*/
protected $steps;
/**
* Store direction for later coordinate modifications
*
* @var ezcGraphVector
*/
protected $direction;
/**
* Store coordinate width modifier for later coordinate modifications
*
* @var float
*/
protected $widthModifier;
/**
* Store coordinate offset for later coordinate modifications
*
* @var float
*/
protected $offset;
/**
* Constructor
*
* @param array $options Default option array
* @return void
* @ignore
*/
public function __construct( array $options = array() )
{
parent::__construct( $options );
$this->properties['angle'] = null;
$this->properties['labelOffset'] = true;
}
/**
* __set
*
* @param mixed $propertyName
* @param mixed $propertyValue
* @throws ezcBaseValueException
* If a submitted parameter was out of range or type.
* @throws ezcBasePropertyNotFoundException
* If a the value for the property options is not an instance of
* @return void
* @ignore
*/
public function __set( $propertyName, $propertyValue )
{
switch ( $propertyName )
{
case 'angle':
if ( !is_numeric( $propertyValue ) )
{
throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float < 360' );
}
$reducement = (int) ( $propertyValue - $propertyValue % 360 );
$this->properties['angle'] = (float) $propertyValue - $reducement;
break;
case 'labelOffset':
if ( !is_bool( $propertyValue ) )
{
throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
}
$this->properties[$propertyName] = (bool) $propertyValue;
break;
default:
return parent::__set( $propertyName, $propertyValue );
}
}
/**
* Determine label angle
*
* Determine the optiomal angle for the axis labels, of no angle has been
* provided by the user.
*
* @param array $steps
* @return void
*/
protected function determineAngle( array $steps, $xSpace, $ySpace, ezcGraphBoundings $axisBoundings )
{
if ( $this->angle === null )
{
$minimumStepWidth = null;
foreach ( $steps as $nr => $step )
{
if ( ( $minimumStepWidth === null ) ||
( $step->width < $minimumStepWidth ) )
{
$minimumStepWidth = $step->width;
}
}
$width = abs(
$axisBoundings->width * $minimumStepWidth * $this->direction->x +
$axisBoundings->height * $minimumStepWidth * $this->direction->y
);
$height = abs(
$ySpace * $this->direction->x +
$xSpace * $this->direction->y
);
$length = sqrt( pow( $width, 2 ) + pow( $height, 2 ) );
$this->angle = rad2deg( acos( $height / $length ) );
}
}
/**
* Determine text offset.
*
* Calculate the label offset and angle, from the configured or evaluated
* text angle.
*
* Returns the text angle in degrees.
*
* @param ezcGraphChartElementAxis $axis
* @param array $steps
* @return float
*/
protected function determineTextOffset( ezcGraphChartElementAxis $axis, array $steps ) {
// Determine additional required axis space by boxes
$firstStep = reset( $steps );
$lastStep = end( $steps );
$axisAngle = -$this->direction->angle( new ezcGraphVector( 1, 0 ) );
$textAngle = $axisAngle +
deg2rad( $this->angle ) +
( $axis->position & ( ezcGraph::TOP | ezcGraph::BOTTOM ) ? deg2rad( 270 ) : deg2rad( 90 ) );
// Ensure angle between 0 and 360 degrees
$degTextAngle = rad2deg( $textAngle );
while ( $degTextAngle < 0 )
{
$degTextAngle += 360.;
}
if ( $this->properties['labelOffset'] )
{
$this->offset =
( $this->angle < 0 ? -1 : 1 ) *
( $axis->position & ( ezcGraph::TOP | ezcGraph::LEFT ) ? 1 : -1 ) *
( 1 - cos( deg2rad( $this->angle * 2 ) ) );
}
else
{
$this->offset = 0;
}
return $degTextAngle;
}
/**
* Calculate label size
*
* Calculate the size of a single lable in a single step.
*
* @param array $steps
* @param int $nr
* @param array $step
* @param float $xSpace
* @param float $ySpace
* @param ezcGraphBoundings $axisBoundings
* @return float
*/
protected function calculateLabelSize( array $steps, $nr, $step, $xSpace, $ySpace, ezcGraphBoundings $axisBoundings )
{
switch ( true )
{
case ( $nr === 0 ):
$labelSize = min(
abs(
$xSpace * 2 * $this->direction->y +
$ySpace * 2 * $this->direction->x ),
abs(
$step->width * $axisBoundings->width * $this->direction->x +
$step->width * $axisBoundings->height * $this->direction->y )
);
break;
case ( $step->isLast ):
$labelSize = min(
abs(
$xSpace * 2 * $this->direction->y +
$ySpace * 2 * $this->direction->x ),
abs(
$steps[$nr - 1]->width * $axisBoundings->width * $this->direction->x +
$steps[$nr - 1]->width * $axisBoundings->height * $this->direction->y )
);
break;
default:
$labelSize = abs(
$step->width * $axisBoundings->width * $this->direction->x +
$step->width * $axisBoundings->height * $this->direction->y
);
break;
}
return $labelSize * cos( deg2rad( $this->angle ) );
}
/**
* Calculate general label length
*
* @param ezcGraphCoordinate $start
* @param ezcGraphCoordinate $end
* @param float $xSpace
* @param float $ySpace
* @param ezcGraphBoundings $axisBoundings
* @return float
*/
protected function calculateLabelLength( ezcGraphCoordinate $start, ezcGraphCoordinate $end, $xSpace, $ySpace, ezcGraphBoundings $axisBoundings )
{
$axisSpaceFactor = abs(
( $this->direction->x == 0 ? 0 :
$this->direction->x * $ySpace / $axisBoundings->width ) +
( $this->direction->y == 0 ? 0 :
$this->direction->y * $xSpace / $axisBoundings->height )
);
$axisWidth = $end->x - $start->x;
$axisHeight = $end->y - $start->y;
$start->x += max( 0., $axisSpaceFactor * $this->offset ) * $axisWidth;
$start->y += max( 0., $axisSpaceFactor * $this->offset ) * $axisHeight;
$end->x += min( 0., $axisSpaceFactor * $this->offset ) * $axisWidth;
$end->y += min( 0., $axisSpaceFactor * $this->offset ) * $axisHeight;
$labelLength = sqrt(
pow(
$xSpace * $this->direction->y +
( $this->labelOffset ?
$axisSpaceFactor * $this->offset * ( $end->x - $start->x ) :
$ySpace * 2 * $this->direction->x
),
2 ) +
pow(
$ySpace * $this->direction->x +
( $this->labelOffset ?
$axisSpaceFactor * $this->offset * ( $end->y - $start->y ) :
$xSpace * 2 * $this->direction->y
),
2 )
);
$this->offset *= $axisSpaceFactor;
return $labelLength;
}
/**
* Render label text.
*
* Render the text of a single label, depending on the position, length and
* rotation of the label.
*
* @param ezcGraphRenderer $renderer
* @param ezcGraphChartElementAxis $axis
* @param ezcGraphCoordinate $position
* @param string $label
* @param float $degTextAngle
* @param float $labelLength
* @param float $labelSize
* @param float $lengthReducement
* @return void
*/
protected function renderLabelText( ezcGraphRenderer $renderer, ezcGraphChartElementAxis $axis, ezcGraphCoordinate $position, $label, $degTextAngle, $labelLength, $labelSize, $lengthReducement )
{
switch ( true )
{
case ( ( ( $degTextAngle >= 0 ) &&
( $degTextAngle < 90 ) &&
( ( $axis->position === ezcGraph::LEFT ) ||
( $axis->position === ezcGraph::RIGHT )
)
) ||
( ( $degTextAngle >= 270 ) &&
( $degTextAngle < 360 ) &&
( ( $axis->position === ezcGraph::TOP ) ||
( $axis->position === ezcGraph::BOTTOM )
)
)
):
$labelBoundings = new ezcGraphBoundings(
$position->x,
$position->y,
$position->x + abs( $labelLength ) - $lengthReducement,
$position->y + $labelSize
);
$labelAlignement = ezcGraph::LEFT | ezcGraph::TOP;
$labelRotation = $degTextAngle;
break;
case ( ( ( $degTextAngle >= 90 ) &&
( $degTextAngle < 180 ) &&
( ( $axis->position === ezcGraph::LEFT ) ||
( $axis->position === ezcGraph::RIGHT )
)
) ||
( ( $degTextAngle >= 180 ) &&
( $degTextAngle < 270 ) &&
( ( $axis->position === ezcGraph::TOP ) ||
( $axis->position === ezcGraph::BOTTOM )
)
)
):
$labelBoundings = new ezcGraphBoundings(
$position->x - abs( $labelLength ) + $lengthReducement,
$position->y,
$position->x,
$position->y + $labelSize
);
$labelAlignement = ezcGraph::RIGHT | ezcGraph::TOP;
$labelRotation = $degTextAngle - 180;
break;
case ( ( ( $degTextAngle >= 180 ) &&
( $degTextAngle < 270 ) &&
( ( $axis->position === ezcGraph::LEFT ) ||
( $axis->position === ezcGraph::RIGHT )
)
) ||
( ( $degTextAngle >= 90 ) &&
( $degTextAngle < 180 ) &&
( ( $axis->position === ezcGraph::TOP ) ||
( $axis->position === ezcGraph::BOTTOM )
)
)
):
$labelBoundings = new ezcGraphBoundings(
$position->x - abs( $labelLength ) + $lengthReducement,
$position->y - $labelSize,
$position->x,
$position->y
);
$labelAlignement = ezcGraph::RIGHT | ezcGraph::BOTTOM;
$labelRotation = $degTextAngle - 180;
break;
case ( ( ( $degTextAngle >= 270 ) &&
( $degTextAngle < 360 ) &&
( ( $axis->position === ezcGraph::LEFT ) ||
( $axis->position === ezcGraph::RIGHT )
)
) ||
( ( $degTextAngle >= 0 ) &&
( $degTextAngle < 90 ) &&
( ( $axis->position === ezcGraph::TOP ) ||
( $axis->position === ezcGraph::BOTTOM )
)
)
):
$labelBoundings = new ezcGraphBoundings(
$position->x,
$position->y,
$position->x - abs( $labelLength ) + $lengthReducement,
$position->y + $labelSize
);
$labelAlignement = ezcGraph::LEFT | ezcGraph::TOP;
$labelRotation = $degTextAngle;
break;
}
$renderer->drawText(
$labelBoundings,
$label,
$labelAlignement,
new ezcGraphRotation(
$labelRotation,
$position
)
);
}
/**
* Render Axis labels
*
* Render labels for an axis.
*
* @param ezcGraphRenderer $renderer Renderer used to draw the chart
* @param ezcGraphBoundings $boundings Boundings of the axis
* @param ezcGraphCoordinate $start Axis starting point
* @param ezcGraphCoordinate $end Axis ending point
* @param ezcGraphChartElementAxis $axis Axis instance
* @return void
*/
public function renderLabels(
ezcGraphRenderer $renderer,
ezcGraphBoundings $boundings,
ezcGraphCoordinate $start,
ezcGraphCoordinate $end,
ezcGraphChartElementAxis $axis,
ezcGraphBoundings $innerBoundings = null )
{
// receive rendering parameters from axis
$steps = $axis->getSteps();
$axisBoundings = new ezcGraphBoundings(
$start->x, $start->y,
$end->x, $end->y
);
// Determine normalized axis direction
$this->direction = new ezcGraphVector(
$end->x - $start->x,
$end->y - $start->y
);
$this->direction->unify();
// Get axis space
$gridBoundings = null;
list( $xSpace, $ySpace ) = $this->getAxisSpace( $renderer, $boundings, $axis, $innerBoundings, $gridBoundings );
// Determine optimal angle if none specified
$this->determineAngle( $steps, $xSpace, $ySpace, $axisBoundings );
$degTextAngle = $this->determineTextOffset( $axis, $steps );
$labelLength = $this->calculateLabelLength( $start, $end, $xSpace, $ySpace, $axisBoundings );
// Draw steps and grid
foreach ( $steps as $nr => $step )
{
$position = new ezcGraphCoordinate(
$start->x + ( $end->x - $start->x ) * $step->position * abs( $this->direction->x ),
$start->y + ( $end->y - $start->y ) * $step->position * abs( $this->direction->y )
);
$stepSize = new ezcGraphCoordinate(
( $end->x - $start->x ) * $step->width,
( $end->y - $start->y ) * $step->width
);
// Calculate label boundings
$labelSize = $this->calculateLabelSize( $steps, $nr, $step, $xSpace, $ySpace, $axisBoundings );
$lengthReducement = min(
abs( tan( deg2rad( $this->angle ) ) * ( $labelSize / 2 ) ),
abs( $labelLength / 2 )
);
$this->renderLabelText( $renderer, $axis, $position, $step->label, $degTextAngle, $labelLength, $labelSize, $lengthReducement );
// Major grid
if ( $axis->majorGrid )
{
$this->drawGrid( $renderer, $gridBoundings, $position, $stepSize, $axis->majorGrid );
}
// Major step
$this->drawStep( $renderer, $position, $this->direction, $axis->position, $this->majorStepSize, $axis->border );
}
}
/**
* Modify chart data position
*
* Optionally additionally modify the coodinate of a data point
*
* @param ezcGraphCoordinate $coordinate Data point coordinate
* @return ezcGraphCoordinate Modified coordinate
*/
public function modifyChartDataPosition( ezcGraphCoordinate $coordinate )
{
return new ezcGraphCoordinate(
$coordinate->x * abs( $this->direction->y ) +
( $coordinate->x * ( 1 - abs( $this->offset ) ) * abs( $this->direction->x ) ) +
( abs( $this->offset ) * abs( $this->direction->x ) ),
$coordinate->y * abs( $this->direction->x ) +
( $coordinate->y * ( 1 - abs( $this->offset ) ) * abs( $this->direction->y ) ) +
( abs( $this->offset ) * abs( $this->direction->y ) )
);
}
}
?>