blob: bf8e2f2a06632d475196e5d8849b2f3868375841 [file] [log] [blame]
<?php
/**
* File containing the ezcGraphChartElementLabeledAxis 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
*/
/**
* Class to represent a labeled axis.
*
* Axis elements represent the axis in a bar, line or radar chart. They are
* chart elements (ezcGraphChartElement) extending from
* ezcGraphChartElementAxis, where additional formatting options can be found.
* You should generally use the axis, which matches your input data best, so
* that the automatic chart layouting works best. Aavailable axis types are:
*
* - ezcGraphChartElementDateAxis
* - ezcGraphChartElementLabeledAxis
* - ezcGraphChartElementLogarithmicalAxis
* - ezcGraphChartElementNumericAxis
*
* The labeled axis will accept any values and converts them to strings. The
* labeled axis does not know about any special meanings of values and
* maintains the order of the given labels with equidistant spaces between all
* values. If your data has a special meaning, like a set of numbers or dates,
* use one of the other more appropriate axis.
*
* Because it is not always possible to fit all labels in a chart you may
* define the count of labels drawn using the $labelCount option. For all other
* labels only a small step will be rendered.
*
* The labeled axis may be used like:
*
* <code>
* $graph = new ezcGraphLineChart();
* $graph->options->fillLines = 210;
* $graph->options->font->maxFontSize = 10;
* $graph->title = 'Error level colors';
* $graph->legend = false;
*
* $graph->yAxis = new ezcGraphChartElementLabeledAxis();
* $graph->yAxis->axisLabelRenderer->showZeroValue = true;
*
* $graph->yAxis->label = 'Color';
* $graph->xAxis->label = 'Error level';
*
* // Add data
* $graph->data['colors'] = new ezcGraphArrayDataSet(
* array(
* 'info' => 'blue',
* 'notice' => 'green',
* 'warning' => 'orange',
* 'error' => 'red',
* 'fatal' => 'red',
* )
* );
*
* $graph->render( 400, 150, 'tutorial_axis_labeled.svg' );
* </code>
*
* @property float $labelCount
* Define count of displayed labels on the axis
*
* @version //autogentag//
* @package Graph
* @mainclass
*/
class ezcGraphChartElementLabeledAxis extends ezcGraphChartElementAxis
{
/**
* Array with labeles for data
*
* @var array
*/
protected $labels = array();
/**
* Labels indexed by their name as key for faster lookups
*
* @var array
*/
protected $labelsIndexed = array();
/**
* Reduced amount of labels which will be displayed in the chart
*
* @var array
*/
protected $displayedLabels = array();
/**
* Maximum count of labels which can be displayed on one axis
* @todo Perhaps base this on the chart size
*/
const MAX_LABEL_COUNT = 10;
/**
* Precalculated steps on the axis
*
* @var array(ezcGraphAxisStep)
*/
protected $steps;
/**
* Constructor
*
* @param array $options Default option array
* @return void
* @ignore
*/
public function __construct( array $options = array() )
{
$this->properties['labelCount'] = null;
$this->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer();
parent::__construct( $options );
}
/**
* __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 'labelCount':
if ( !is_numeric( $propertyValue ) ||
( $propertyValue <= 1 ) )
{
throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' );
}
$this->properties['labelCount'] = (int) $propertyValue;
break;
default:
parent::__set( $propertyName, $propertyValue );
break;
}
}
/**
* Increase the keys of all elements in the array up from the start key, to
* insert an additional element at the correct position.
*
* @param array $array Array
* @param int $startKey Key to increase keys from
* @return array Updated array
*/
protected function increaseKeys( array $array, $startKey )
{
foreach ( $array as $key => $value )
{
if ( $key === $startKey )
{
// Recursive check, if next key should be increased, too
if ( isset ( $array[$key + 1] ) )
{
$array = $this->increaseKeys( $array, $key + 1 );
}
// Increase key
$array[$key + 1] = $array[$key];
unset( $array[$key] );
}
}
return $array;
}
/**
* Provide initial set of labels
*
* This method may be used to provide an ordered set of labels, containing
* labels, which are not available in the datasets or to provide a label
* order different to the one in the given dataset.
*
* @param array $labels
* @return void
*/
public function provideLabels( array $labels )
{
$this->addData( $labels );
}
/**
* Add data for this axis
*
* @param array $values Value which will be displayed on this axis
* @return void
*/
public function addData( array $values )
{
$position = 0;
foreach ( $values as $label )
{
$label = (string) $label;
if ( !in_array( $label, $this->labels, true ) )
{
if ( isset( $this->labels[$position] ) )
{
$this->labels = $this->increaseKeys( $this->labels, $position );
$this->labels[$position++] = $label;
}
else
{
$this->labels[$position++] = $label;
}
}
else
{
$position = array_search( $label, $this->labels, true ) + 1;
}
}
ksort( $this->labels );
$this->labelsIndexed = array_flip( $this->labels );
$this->properties['initialized'] = true;
}
/**
* Calculate axis bounding values on base of the assigned values
*
* @abstract
* @access public
* @return void
*/
public function calculateAxisBoundings()
{
$this->steps = array();
// Apply label format callback function
if ( $this->properties['labelCallback'] !== null )
{
foreach ( $this->labels as $nr => $label )
{
$this->labels[$nr] = call_user_func_array(
$this->properties['labelCallback'],
array(
$label,
$nr
)
);
}
}
$labelCount = count( $this->labels ) - 1;
if ( $labelCount === 0 )
{
// Create single only step
$this->steps = array(
new ezcGraphAxisStep(
0,
1,
reset( $this->labels ),
array(),
true,
true
),
);
return true;
}
if ( $this->properties['labelCount'] === null )
{
if ( $labelCount <= self::MAX_LABEL_COUNT )
{
$stepSize = 1 / $labelCount;
foreach ( $this->labels as $nr => $label )
{
$this->steps[] = new ezcGraphAxisStep(
$stepSize * $nr,
$stepSize,
$label,
array(),
$nr === 0,
$nr === $labelCount
);
}
// @TODO: This line is deprecated and only build for
// deprecated getLabel()
$this->displayedLabels = $this->labels;
return true;
}
for ( $div = self::MAX_LABEL_COUNT; $div > 1; --$div )
{
if ( ( $labelCount % $div ) === 0 )
{
// @TODO: This part is deprecated and only build for
// deprecated getLabel()
$step = $labelCount / $div;
foreach ( $this->labels as $nr => $label )
{
if ( ( $nr % $step ) === 0 )
{
$this->displayedLabels[] = $label;
}
}
// End of deprecated part
break;
}
}
}
else
{
$div = false;
}
// Build up step array
if ( $div > 2 )
{
$step = $labelCount / $div;
$stepSize = 1 / $div;
$minorStepSize = $stepSize / $step;
foreach ( $this->labels as $nr => $label )
{
if ( ( $nr % $step ) === 0 )
{
$mainstep = new ezcGraphAxisStep(
$stepSize * ( $nr / $step ),
$stepSize,
$label,
array(),
$nr === 0,
$nr === $labelCount
);
$this->steps[] = $mainstep;
}
else
{
$mainstep->childs[] = new ezcGraphAxisStep(
$mainstep->position + $minorStepSize * ( $nr % $step ),
$minorStepSize
);
}
}
}
else
{
if ( $this->properties['labelCount'] === null )
{
$floatStep = $labelCount / ( self::MAX_LABEL_COUNT - 1 );
}
else
{
$floatStep = $labelCount / min( $labelCount, $this->properties['labelCount'] - 1 );
}
$position = 0;
$minorStepSize = 1 / $labelCount;
foreach ( $this->labels as $nr => $label )
{
if ( $nr >= $position )
{
$position += $floatStep;
// Add as major step
$mainstep = new ezcGraphAxisStep(
$minorStepSize * $nr,
ceil( $position - $nr ) * $minorStepSize,
$label,
array(),
$nr === 0,
$nr === $labelCount
);
// @TODO: This line is deprecated and only build for
// deprecated getLabel()
$this->displayedLabels[] = $label;
$this->steps[] = $mainstep;
}
else
{
$mainstep->childs[] = new ezcGraphAxisStep(
$minorStepSize * $nr,
$minorStepSize
);
}
}
}
}
/**
* Return array of steps on this axis
*
* @return array( ezcGraphAxisStep )
*/
public function getSteps()
{
return $this->steps;
}
/**
* Get coordinate for a dedicated value on the chart
*
* @param string $value Value to determine position for
* @return float Position on chart
*/
public function getCoordinate( $value )
{
if ( ( $value === false ) ||
( $value === null ) ||
( !isset( $this->labelsIndexed[$value] ) ) )
{
switch ( $this->position )
{
case ezcGraph::LEFT:
case ezcGraph::TOP:
return 0.;
case ezcGraph::RIGHT:
case ezcGraph::BOTTOM:
return 1.;
}
}
else
{
$key = $this->labelsIndexed[$value];
switch ( $this->position )
{
case ezcGraph::LEFT:
case ezcGraph::TOP:
if ( count( $this->labels ) > 1 )
{
return (float) $key / ( count ( $this->labels ) - 1 );
}
else
{
return 0;
}
case ezcGraph::BOTTOM:
case ezcGraph::RIGHT:
if ( count( $this->labels ) > 1 )
{
return (float) 1 - $key / ( count ( $this->labels ) - 1 );
}
else
{
return 1;
}
}
}
}
/**
* Return count of minor steps
*
* @return integer Count of minor steps
*/
public function getMinorStepCount()
{
return 0;
}
/**
* Return count of major steps
*
* @return integer Count of major steps
*/
public function getMajorStepCount()
{
return max( count( $this->displayedLabels ) - 1, 1 );
}
/**
* Get label for a dedicated step on the axis
*
* @param integer $step Number of step
* @return string label
*/
public function getLabel( $step )
{
if ( isset( $this->displayedLabels[$step] ) )
{
return $this->displayedLabels[$step];
}
else
{
return false;
}
}
/**
* Is zero step
*
* Returns true if the given step is the one on the initial axis position
*
* @param int $step Number of step
* @return bool Status If given step is initial axis position
*/
public function isZeroStep( $step )
{
return !$step;
}
}
?>