blob: 9d43c177c85f1bed6bbd78d3b819ca5a592a4a94 [file] [log] [blame]
<?php
/**
* File containing the three dimensional renderer
*
* 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//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Class to transform chart primitives into image primitives. This renderer
* renders the charts in a isometric three dimensional view.
*
* The class options are defined in the class {@link ezcGraphRenderer3dOptions}
* extending the basic renderer options in {@link ezcGraphRendererOptions}.
*
* <code>
* $graph = new ezcGraphPieChart();
* $graph->palette = new ezcGraphPaletteEzRed();
* $graph->title = 'Access statistics';
* $graph->options->label = '%2$d (%3$.1f%%)';
*
* $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
* 'Mozilla' => 19113,
* 'Explorer' => 10917,
* 'Opera' => 1464,
* 'Safari' => 652,
* 'Konqueror' => 474,
* ) );
* $graph->data['Access statistics']->highlight['Explorer'] = true;
*
* $graph->renderer = new ezcGraphRenderer3d();
*
* $graph->renderer->options->moveOut = .2;
*
* $graph->renderer->options->pieChartOffset = 63;
*
* $graph->renderer->options->pieChartGleam = .3;
* $graph->renderer->options->pieChartGleamColor = '#FFFFFF';
*
* $graph->renderer->options->pieChartShadowSize = 5;
* $graph->renderer->options->pieChartShadowColor = '#000000';
*
* $graph->renderer->options->legendSymbolGleam = .5;
* $graph->renderer->options->legendSymbolGleamSize = .9;
* $graph->renderer->options->legendSymbolGleamColor = '#FFFFFF';
*
* $graph->renderer->options->pieChartSymbolColor = '#55575388';
*
* $graph->renderer->options->pieChartHeight = 5;
* $graph->renderer->options->pieChartRotation = .8;
*
* $graph->render( 400, 150, 'tutorial_pie_chart_3d.svg' );
* </code>
*
* @version //autogentag//
* @package Graph
* @mainclass
*/
class ezcGraphRenderer3d
extends
ezcGraphRenderer
implements
ezcGraphStackedBarsRenderer
{
/**
* Pie segment labels divided into two array, containing the labels on the
* left and right side of the pie chart center.
*
* @var array
*/
protected $pieSegmentLabels = array(
0 => array(),
1 => array(),
);
/**
* Contains the boundings used for pie segments
*
* @var ezcGraphBoundings
*/
protected $pieSegmentBoundings = false;
/**
* Array with symbols for post processing, which ensures, that the symbols
* are rendered topmost.
*
* @var array
*/
protected $linePostSymbols = array();
/**
* Array containing lines from the axis and grid which should be redrawn on
* top of the data.
*
* @var array
*/
protected $frontLines = array();
/**
* Collects circle sectors to draw shadow in background of all circle
* sectors.
*
* @var array
*/
protected $circleSectors = array();
/**
* Collects bar sides to draw them in a post processing step to simulate
* a simple z buffer.
* array(
* array(
* 'index' => (int) // used for sorting
* 'context' => ezcGraphContext // context of call
* 'method' => (string) // method of driver to call
* 'parameters' => array // parameters for method call
* ), ...
* )
*
* @var array
*/
protected $barPostProcessing = array();
/**
* Options
*
* @var ezcGraphRenderer3dOptions
*/
protected $options;
/**
* Depth of displayed pseudo three dimensional line chart elements.
*
* @var float
*/
protected $depth = false;
/**
* Factor to reduce the width according to depth
*
* @var float
*/
protected $xDepthFactor = false;
/**
* Factor to reduce the height according to depth
*
* @var float
*/
protected $yDepthFactor = false;
/**
* Boundings for the chart data
*
* @var ezcGraphBoundings
*/
protected $dataBoundings = false;
/**
* Collect axis labels, so that the axis are drawn, when all axis spaces
* are known.
*
* @var array
*/
protected $axisLabels = array();
/**
* Constructor
*
* @param array $options Default option array
* @return void
* @ignore
*/
public function __construct( array $options = array() )
{
$this->options = new ezcGraphRenderer3dOptions( $options );
}
/**
* __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 'options':
return $this->options;
default:
return parent::__get( $propertyName );
}
}
/**
* Calculate the display coordinate from a coordinate
*
* Calculates the display coordinate of a coordinate depending on the
* depth setting and the distance of the coordinate to the front of the
* chart.
*
* @param ezcGraphCoordinate $c Coordinate
* @param float $front Distance to front (0 - 1)
* @return ezcGraphCoordinate Resulting coordinate
*/
protected function get3dCoordinate( ezcGraphCoordinate $c, $front = 1. )
{
return new ezcGraphCoordinate(
( $c->x - $this->dataBoundings->x0 ) * $this->xDepthFactor + $this->dataBoundings->x0 + $this->depth * $front,
( $c->y - $this->dataBoundings->y0 ) * $this->yDepthFactor + $this->dataBoundings->y0 + $this->depth * ( 1 - $front )
);
}
/**
* Draw pie segment
*
* Draws a single pie segment
*
* @param ezcGraphBoundings $boundings Chart boundings
* @param ezcGraphContext $context Context of call
* @param ezcGraphColor $color Color of pie segment
* @param float $startAngle Start angle
* @param float $endAngle End angle
* @param mixed $label Label of pie segment
* @param bool $moveOut Move out from middle for hilighting
* @return void
*/
public function drawPieSegment(
ezcGraphBoundings $boundings,
ezcGraphContext $context,
ezcGraphColor $color,
$startAngle = .0,
$endAngle = 360.,
$label = false,
$moveOut = false )
{
// Apply offset
$startAngle += $this->options->pieChartOffset;
$endAngle += $this->options->pieChartOffset;
// Calculate position and size of pie
$center = new ezcGraphCoordinate(
$boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2,
$boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2
- $this->options->pieChartHeight / 2
);
// Limit radius to fourth of width and half of height at maximum
$radius = min(
( $boundings->x1 - $boundings->x0 ) * $this->options->pieHorizontalSize,
( $boundings->y1 - $boundings->y0 ) * $this->options->pieVerticalSize
);
// Move pie segment out of the center
if ( $moveOut )
{
$direction = ( $endAngle + $startAngle ) / 2;
$center = new ezcGraphCoordinate(
$center->x + $this->options->moveOut * $radius * cos( deg2rad( $direction ) ),
$center->y + $this->options->moveOut * $radius * sin( deg2rad( $direction ) ) * $this->options->pieChartRotation
);
}
// Add circle sector to queue
$this->circleSectors[] = array(
'center' => $center,
'context' => $context,
'width' => $radius * 2 * ( 1 - $this->options->moveOut ),
'height' => $radius * 2 * ( 1 - $this->options->moveOut ) * $this->options->pieChartRotation - $this->options->pieChartHeight,
'start' => $startAngle,
'end' => $endAngle,
'color' => $color,
);
if ( $label )
{
// Determine position of label
$direction = ( $endAngle + $startAngle ) / 2;
$pieSegmentCenter = new ezcGraphCoordinate(
$center->x + cos( deg2rad( $direction ) ) * $radius,
$center->y + sin( deg2rad( $direction ) ) * $radius * $this->options->pieChartRotation
);
// Split labels up into left a right site and index them on their
// y position
$this->pieSegmentLabels[(int) ($pieSegmentCenter->x > $center->x)][(int) ( $pieSegmentCenter->y * 100 )] = array(
new ezcGraphCoordinate(
$center->x + cos( deg2rad( $direction ) ) * $radius * 2 / 3 * ( 1 - $this->options->moveOut ),
$center->y + sin( deg2rad( $direction ) ) * ( $radius - $this->options->pieChartHeight ) * 2 / 3 * ( 1 - $this->options->moveOut ) * $this->options->pieChartRotation
),
$label,
$context,
);
}
if ( !$this->pieSegmentBoundings )
{
$this->pieSegmentBoundings = $boundings;
}
}
/**
* Draws the collected pie segment labels
*
* All labels are collected and drawn later to be able to partition the
* available space for the labels woth knowledge of the overall label
* count and their required size and optimal position.
*
* @return void
*/
protected function finishPieSegmentLabels()
{
if ( $this->pieSegmentBoundings === false )
{
return true;
}
$boundings = $this->pieSegmentBoundings;
// Calculate position and size of pie
$center = new ezcGraphCoordinate(
$boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2,
$boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2
);
// Limit radius to fourth of width and half of height at maximum
$radius = min(
( $boundings->width ) * $this->options->pieHorizontalSize,
( $boundings->height ) * $this->options->pieVerticalSize
);
$pieChartHeight = min(
$radius * 2 + $radius / max( 1, count ( $this->pieSegmentLabels[0] ), count( $this->pieSegmentLabels[1] ) ) * 4,
$boundings->height
);
$pieChartYPosition = $boundings->y0 + ( ( $boundings->height ) - $pieChartHeight ) / 2;
// Calculate maximum height of labels
$labelHeight = min(
( count( $this->pieSegmentLabels[0] )
? $pieChartHeight / count( $this->pieSegmentLabels[0] )
: $pieChartHeight
),
( count( $this->pieSegmentLabels[1] )
? $pieChartHeight / count( $this->pieSegmentLabels[1] )
: $pieChartHeight
),
( $pieChartHeight ) * $this->options->maxLabelHeight
);
$symbolSize = $this->options->symbolSize;
foreach ( $this->pieSegmentLabels as $side => $labelPart )
{
$minHeight = $pieChartYPosition;
$toShare = $pieChartHeight - count( $labelPart ) * $labelHeight;
// Sort to draw topmost label first
ksort( $labelPart );
$sign = ( $side ? -1 : 1 );
foreach ( $labelPart as $height => $label )
{
$height = (int) ( $height / 100 );
if ( ( $height - $labelHeight / 2 ) > $minHeight )
{
$share = min( $toShare, ( $height - $labelHeight / 2) - $minHeight );
$minHeight += $share;
$toShare -= $share;
}
// Determine position of label
$minHeight += max( 0, $height - $minHeight - $labelHeight ) / $pieChartHeight * $toShare;
$verticalDistance = ( $center->y - $minHeight - $labelHeight / 2 ) / $radius;
$labelPosition = new ezcGraphCoordinate(
$center->x -
$sign * (
abs( $verticalDistance ) > 1
// If vertical distance to center is greater then the
// radius, use the centerline for the horizontal
// position
? max (
5,
abs( $label[0]->x - $center->x )
)
// Else place the label outside of the pie chart
: ( cos ( asin ( $verticalDistance ) ) * $radius +
$symbolSize * (int) $this->options->showSymbol
)
),
$minHeight + $labelHeight / 2
);
if ( $this->options->showSymbol )
{
// Draw label
$this->driver->drawLine(
$label[0],
$labelPosition,
$this->options->pieChartSymbolColor,
1
);
$this->driver->drawCircle(
$label[0],
$symbolSize,
$symbolSize,
$this->options->pieChartSymbolColor,
true
);
$this->driver->drawCircle(
$labelPosition,
$symbolSize,
$symbolSize,
$this->options->pieChartSymbolColor,
true
);
}
$this->addElementReference( $label[2],
$this->driver->drawTextBox(
$label[1],
new ezcGraphCoordinate(
( !$side ? $boundings->x0 : $labelPosition->x + $symbolSize ),
$minHeight
),
( !$side ? $labelPosition->x - $boundings->x0 - $symbolSize : $boundings->x1 - $labelPosition->x - $symbolSize ),
$labelHeight,
( !$side ? ezcGraph::RIGHT : ezcGraph::LEFT ) | ezcGraph::MIDDLE
)
);
// Add used space to minHeight
$minHeight += $labelHeight;
}
}
}
/**
* Draws the collected circle sectors
*
* All circle sectors are collected and drawn later to be able to render
* the shadows of the pie segments in the back of all pie segments, and
* ensure the correct drawing order for all pie segment elements.
*
* @return void
*/
protected function finishCirleSectors()
{
$zBuffer = array();
$shadows = array();
$shadowCenter = false;
$shadowEndAngle = false;
// Add circle sector sides to simple z buffer prioriry list
foreach ( $this->circleSectors as $circleSector )
{
// Draw shadow if wanted
if ( $this->options->pieChartShadowSize > 0 )
{
if ( $shadowEndAngle === false )
{
$shadowStartAngle = $circleSector['start'];
$shadowEndAngle = $circleSector['end'];
$shadowCenter = $circleSector['center'];
}
elseif ( $circleSector['center'] == $shadowCenter )
{
$shadowEndAngle = $circleSector['end'];
}
else
{
$shadows[] = array(
'center' => $shadowCenter,
'start' => $shadowStartAngle,
'end' => $shadowEndAngle,
'width' => $circleSector['width'],
'height' => $circleSector['height'],
);
$shadowCenter = $circleSector['center'];
$shadowStartAngle = $circleSector['start'];
$shadowEndAngle = $circleSector['end'];
}
}
$darkenedColor = $circleSector['color']->darken( $this->options->dataBorder );
$center = (int) ( $circleSector['center']->y + sin( deg2rad( $circleSector['start'] + ( $circleSector['end'] - $circleSector['start'] ) / 2 ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight / 2 + 1 );
$zBuffer[$center][] = array(
'method' => 'drawCircularArc',
'paramenters' => array(
$circleSector['center'],
$circleSector['width'],
$circleSector['height'],
$this->options->pieChartHeight,
$circleSector['start'],
$circleSector['end'],
$circleSector['color']
)
);
// Left side
$polygonPoints = array(
$circleSector['center'],
new ezcGraphCoordinate(
$circleSector['center']->x,
$circleSector['center']->y + $this->options->pieChartHeight
),
new ezcGraphCoordinate(
$circleSector['center']->x + cos( deg2rad( $circleSector['start'] ) ) * $circleSector['width'] / 2,
$circleSector['center']->y + sin( deg2rad( $circleSector['start'] ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight
),
new ezcGraphCoordinate(
$circleSector['center']->x + cos( deg2rad( $circleSector['start'] ) ) * $circleSector['width'] / 2,
$circleSector['center']->y + sin( deg2rad( $circleSector['start'] ) ) * $circleSector['height'] / 2
),
);
// Get average y coordinate for polygon to use for zBuffer
$center = 0;
foreach ( $polygonPoints as $point )
{
$center += $point->y;
}
$center = (int) ( $center / count( $polygonPoints ) );
$zBuffer[$center][] = array(
'method' => 'drawPolygon',
'paramenters' => array(
$polygonPoints,
$circleSector['color'],
true
),
);
$zBuffer[$center][] = array(
'method' => 'drawPolygon',
'paramenters' => array(
$polygonPoints,
$darkenedColor,
false
),
);
// Right side
$polygonPoints = array(
$circleSector['center'],
new ezcGraphCoordinate(
$circleSector['center']->x,
$circleSector['center']->y + $this->options->pieChartHeight
),
new ezcGraphCoordinate(
$circleSector['center']->x + cos( deg2rad( $circleSector['end'] ) ) * $circleSector['width'] / 2,
$circleSector['center']->y + sin( deg2rad( $circleSector['end'] ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight
),
new ezcGraphCoordinate(
$circleSector['center']->x + cos( deg2rad( $circleSector['end'] ) ) * $circleSector['width'] / 2,
$circleSector['center']->y + sin( deg2rad( $circleSector['end'] ) ) * $circleSector['height'] / 2
),
);
// Get average y coordinate for polygon to use for zBuffer
$center = 0;
foreach ( $polygonPoints as $point )
{
$center += $point->y;
}
$center = (int) ( $center / count( $polygonPoints ) );
$zBuffer[$center][] = array(
'method' => 'drawPolygon',
'paramenters' => array(
$polygonPoints,
$circleSector['color'],
true
),
);
$zBuffer[$center][] = array(
'method' => 'drawPolygon',
'paramenters' => array(
$polygonPoints,
$darkenedColor,
false
),
);
}
if ( $this->options->pieChartShadowSize > 0 )
{
$shadows[] = array(
'center' => $shadowCenter,
'start' => $shadowStartAngle,
'end' => $shadowEndAngle,
'width' => $circleSector['width'],
'height' => $circleSector['height'],
);
}
// Draw collected shadows
foreach ( $shadows as $circleSector )
{
for ( $i = $this->options->pieChartShadowSize; $i > 0; --$i )
{
$startAngle = $circleSector['start'];
$endAngle = $circleSector['end'];
$startAngle = $circleSector['start'] - ( $this->options->pieChartShadowSize - $i );
$endAngle = $circleSector['end'] + ( $this->options->pieChartShadowSize - $i );
if ( ( $endAngle - $startAngle ) >= 360 )
{
$this->driver->drawCircle(
new ezcGraphCoordinate(
$circleSector['center']->x,
$circleSector['center']->y + $this->options->pieChartHeight
),
$circleSector['width'] + $i * 2,
$circleSector['height'] + $i * 2,
$this->options->pieChartShadowColor->transparent( 1 - ( $this->options->pieChartShadowTransparency / $this->options->pieChartShadowSize ) ),
true
);
}
else
{
$this->driver->drawCircleSector(
new ezcGraphCoordinate(
$circleSector['center']->x,
$circleSector['center']->y + $this->options->pieChartHeight
),
$circleSector['width'] + $i * 2,
$circleSector['height'] + $i * 2,
$startAngle,
$endAngle,
$this->options->pieChartShadowColor->transparent( 1 - ( $this->options->pieChartShadowTransparency / $this->options->pieChartShadowSize ) ),
true
);
}
}
}
ksort( $zBuffer );
foreach ( $zBuffer as $sides )
{
foreach ( $sides as $side )
{
call_user_func_array( array( $this->driver, $side['method'] ), $side['paramenters'] );
}
}
// Draw circle sector for front
foreach ( $this->circleSectors as $circleSector )
{
$this->addElementReference( $circleSector['context'],
$this->driver->drawCircleSector(
$circleSector['center'],
$circleSector['width'],
$circleSector['height'],
$circleSector['start'],
$circleSector['end'],
$circleSector['color'],
true
)
);
if ( $this->options->pieChartGleam !== false )
{
$gradient = new ezcGraphLinearGradient(
$circleSector['center'],
new ezcGraphCoordinate(
$circleSector['center']->x - $circleSector['width'] / 2,
$circleSector['center']->y - $circleSector['height'] / 2
),
$this->options->pieChartGleamColor->transparent( 1 ),
$this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam )
);
$this->addElementReference( $circleSector['context'],
$this->driver->drawCircleSector(
$circleSector['center'],
$circleSector['width'] - $this->options->pieChartGleamBorder * 2,
$circleSector['height'] - $this->options->pieChartGleamBorder * 2 * $this->options->pieChartRotation,
$circleSector['start'],
$circleSector['end'],
$gradient,
true
)
);
}
$darkenedColor = $circleSector['color']->darken( $this->options->dataBorder );
$this->driver->drawCircleSector(
$circleSector['center'],
$circleSector['width'],
$circleSector['height'],
$circleSector['start'],
$circleSector['end'],
$darkenedColor,
false
);
if ( $this->options->pieChartGleam !== false )
{
$radialGradient = new ezcGraphRadialGradient(
new ezcGraphCoordinate(
$circleSector['center']->x + $circleSector['width'] / 2 * cos( deg2rad( 135 ) ),
$circleSector['center']->y + $circleSector['height'] / 2 * sin( deg2rad( 135 ) )
),
$circleSector['width'],
$circleSector['height'],
$this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam ),
$this->options->pieChartGleamColor->transparent( .8 )
);
$this->driver->drawCircularArc(
$circleSector['center'],
$circleSector['width'],
$circleSector['height'],
0,
$circleSector['start'],
$circleSector['end'],
$radialGradient,
false
);
}
}
}
/**
* Draw collected front lines
*
* Draw all grid and axis lines, which should be redrawn in front of the
* data.
*
* @return void
*/
protected function finishFrontLines()
{
foreach ( $this->frontLines as $line )
{
$this->driver->drawLine(
$line[0],
$line[1],
$line[2],
$line[3]
);
}
}
/**
* Draw the collected line symbols
*
* Symbols for the data lines are collected and delayed to ensure that
* they are not covered and hidden by other data lines.
*
* @return void
*/
protected function finishLineSymbols()
{
foreach ( $this->linePostSymbols as $symbol )
{
$this->addElementReference( $symbol['context'],
$this->drawSymbol(
$symbol['boundings'],
$symbol['color'],
$symbol['symbol']
)
);
}
}
/**
* Draws a bar with a rectangular ground shape.
*
* @param ezcGraphContext $context
* @param ezcGraphColor $color
* @param ezcGraphCoordinate $position
* @param float $barWidth
* @param float $offset
* @param float $axisPosition
* @param float $startDepth
* @param float $midDepth
* @param float $endDepth
* @return void
*/
protected function drawRectangularBar(
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth )
{
$barPolygonArray = array(
new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset,
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
),
new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset,
$this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
),
new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth,
$this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
),
new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth,
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
),
);
// Draw right bar side
$this->barPostProcessing[] = array(
'index' => $barPolygonArray[2]->x + ( 1 - $position->y ),
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( $barPolygonArray[2], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $endDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $endDepth ),
),
$color->darken( $this->options->barDarkenSide ),
true
),
);
// Draw top side
$this->barPostProcessing[] = array(
'index' => $barPolygonArray[1]->x + ( 1 - $position->y ),
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
( $barPolygonArray[1]->y < $barPolygonArray[3]->y
? array(
$this->get3dCoordinate( $barPolygonArray[1], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $endDepth ),
$this->get3dCoordinate( $barPolygonArray[1], $endDepth ),
)
: array(
$this->get3dCoordinate( $barPolygonArray[0], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $endDepth ),
$this->get3dCoordinate( $barPolygonArray[0], $endDepth ),
)
),
$color->darken( $this->options->barDarkenTop ),
true
),
);
// Draw top side gleam
if ( $this->options->barChartGleam !== false )
{
$this->barPostProcessing[] = array(
'index' => $barPolygonArray[1]->x + 1 + ( 1 - $position->y ),
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
( $barPolygonArray[1]->y < $barPolygonArray[3]->y
? array(
$this->get3dCoordinate( $barPolygonArray[1], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $endDepth ),
$this->get3dCoordinate( $barPolygonArray[1], $endDepth ),
)
: array(
$this->get3dCoordinate( $barPolygonArray[0], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $endDepth ),
$this->get3dCoordinate( $barPolygonArray[0], $endDepth ),
)
),
new ezcGraphLinearGradient(
( $barPolygonArray[1]->y < $barPolygonArray[3]->y
? $this->get3dCoordinate( $barPolygonArray[2], $endDepth )
: $this->get3dCoordinate( $barPolygonArray[3], $endDepth )
),
( $barPolygonArray[1]->y < $barPolygonArray[3]->y
? $this->get3dCoordinate( $barPolygonArray[1], $startDepth )
: $this->get3dCoordinate( $barPolygonArray[0], $startDepth )
),
ezcGraphColor::fromHex( '#FFFFFFFF' ),
ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam )
),
true
),
);
}
// Draw front side
$this->barPostProcessing[] = array(
'index' => $barPolygonArray[1]->x + ( 1 - $position->y ),
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( $barPolygonArray[0], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[1], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
),
$color,
true
),
);
// Draw front side gleam
if ( $this->options->barChartGleam !== false )
{
$this->barPostProcessing[] = array(
'index' => $barPolygonArray[1]->x + 1 + ( 1 - $position->y ),
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( $barPolygonArray[0], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[1], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[2], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
),
new ezcGraphLinearGradient(
$this->get3dCoordinate( $barPolygonArray[3], $startDepth ),
$this->get3dCoordinate( $barPolygonArray[1], $startDepth ),
ezcGraphColor::fromHex( '#FFFFFFFF' ),
ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam )
),
true
),
);
}
}
/**
* Draws a bar with a diamond ground shape.
*
* @param ezcGraphContext $context
* @param ezcGraphColor $color
* @param ezcGraphCoordinate $position
* @param float $barWidth
* @param float $offset
* @param float $axisPosition
* @param float $startDepth
* @param float $midDepth
* @param float $endDepth
* @return void
*/
protected function drawDiamondBar(
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth )
{
$barCoordinateArray = array(
// The bottom point of the diamond is moved to .7 instead
// of .5 because it looks more correct, even it is wrong...
'x' => array(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset,
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth * .7,
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth,
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth * .3,
),
'y' => array(
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ),
),
);
// Left side
$this->barPostProcessing[] = array(
'index' => $barCoordinateArray['x'][0],
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $barCoordinateArray['y'][0] ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $barCoordinateArray['y'][1] ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][1] ), $startDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][0] ), $startDepth ),
),
$color,
true
),
);
// Right side
$this->barPostProcessing[] = array(
'index' => $barCoordinateArray['x'][1],
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $barCoordinateArray['y'][0] ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $barCoordinateArray['y'][1] ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][1] ), $startDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][0] ), $startDepth ),
),
$color->darken( $this->options->barDarkenSide ),
true
),
);
$topLocation = min(
$barCoordinateArray['y'][0],
$barCoordinateArray['y'][1]
);
// Top side
$this->barPostProcessing[] = array(
'index' => $barCoordinateArray['x'][0],
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $topLocation ), $startDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][3], $topLocation ), $endDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ),
),
$color->darken( $this->options->barDarkenTop ),
true
),
);
// Top side gleam
if ( $this->options->barChartGleam !== false )
{
$this->barPostProcessing[] = array(
'index' => $barCoordinateArray['x'][0] + 1,
'method' => 'drawPolygon',
'context' => $context,
'parameters' => array(
array(
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $topLocation ), $startDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][3], $topLocation ), $endDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ),
),
new ezcGraphLinearGradient(
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ),
$this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ),
ezcGraphColor::fromHex( '#FFFFFFFF' ),
ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam )
),
true
),
);
}
}
/**
* Draws a bar with a circular ground shape.
*
* @param ezcGraphContext $context
* @param ezcGraphColor $color
* @param ezcGraphCoordinate $position
* @param float $barWidth
* @param float $offset
* @param float $axisPosition
* @param float $startDepth
* @param float $midDepth
* @param float $endDepth
* @param int $symbol
* @return void
*/
protected function drawCircularBar(
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth,
$symbol )
{
$barCenterTop = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth / 2,
$this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
$barCenterBottom = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth / 2,
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
if ( $barCenterTop->y > $barCenterBottom->y )
{
$tmp = $barCenterTop;
$barCenterTop = $barCenterBottom;
$barCenterBottom = $tmp;
}
$this->barPostProcessing[] = array(
'index' => $barCenterBottom->x,
'method' => 'drawCircularArc',
'context' => $context,
'parameters' => array(
$this->get3dCoordinate( $barCenterTop, $midDepth ),
$barWidth,
$barWidth / 2,
( $barCenterBottom->y - $barCenterTop->y ) * $this->yDepthFactor,
0,
180,
$color
),
);
$this->barPostProcessing[] = array(
'index' => $barCenterBottom->x + 1,
'method' => 'drawCircle',
'context' => $context,
'parameters' => array(
$top = $this->get3dCoordinate( $barCenterTop, $midDepth ),
$barWidth,
$barWidth / 2,
( $symbol === ezcGraph::CIRCLE
? new ezcGraphLinearGradient(
new ezcGraphCoordinate(
$top->x - $barWidth / 2,
$top->y
),
new ezcGraphCoordinate(
$top->x + $barWidth / 2,
$top->y
),
$color->darken( $this->options->barDarkenTop ),
$color
)
: $color
)
),
);
}
/**
* Draw bar
*
* Draws a bar as a data element in a line chart
*
* @param ezcGraphBoundings $boundings Chart boundings
* @param ezcGraphContext $context Context of call
* @param ezcGraphColor $color Color of line
* @param ezcGraphCoordinate $position Position of data point
* @param float $stepSize Space which can be used for bars
* @param int $dataNumber Number of dataset
* @param int $dataCount Count of datasets in chart
* @param int $symbol Symbol to draw for line
* @param float $axisPosition Position of axis for drawing filled lines
* @return void
*/
public function drawBar(
ezcGraphBoundings $boundings,
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $position,
$stepSize,
$dataNumber = 1,
$dataCount = 1,
$symbol = ezcGraph::NO_SYMBOL,
$axisPosition = 0. )
{
// Apply margin
$margin = $stepSize * $this->options->barMargin;
$padding = $stepSize * $this->options->barPadding;
$barWidth = ( $stepSize - $margin ) / $dataCount - $padding;
$offset = - $stepSize / 2 + $margin / 2 + ( $dataCount - $dataNumber - 1 ) * ( $padding + $barWidth ) + $padding / 2;
if ( $barWidth < 0 )
{
$offset -= $barWidth = abs( $barWidth );
}
$startDepth = $this->options->barMargin;
$midDepth = .5;
$endDepth = 1 - $this->options->barMargin;
switch ( $symbol )
{
case ezcGraph::NO_SYMBOL:
$this->drawRectangularBar(
$context,
$color,
$position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth
);
break;
case ezcGraph::DIAMOND:
$this->drawDiamondBar(
$context,
$color,
$position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth
);
break;
case ezcGraph::BULLET:
case ezcGraph::CIRCLE:
$this->drawCircularBar(
$context,
$color,
$position,
$barWidth,
$offset,
$axisPosition,
$startDepth,
$midDepth,
$endDepth,
$symbol
);
break;
}
}
/**
* Draw stacked bar
*
* Draws a stacked bar part as a data element in a line chart
*
* @param ezcGraphBoundings $boundings Chart boundings
* @param ezcGraphContext $context Context of call
* @param ezcGraphColor $color Color of line
* @param ezcGraphCoordinate $start
* @param ezcGraphCoordinate $position
* @param float $stepSize Space which can be used for bars
* @param int $symbol Symbol to draw for line
* @param float $axisPosition Position of axis for drawing filled lines
* @return void
*/
public function drawStackedBar(
ezcGraphBoundings $boundings,
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $start,
ezcGraphCoordinate $position,
$stepSize,
$symbol = ezcGraph::NO_SYMBOL,
$axisPosition = 0. )
{
// Apply margin
$margin = $stepSize * $this->options->barMargin;
$barWidth = $stepSize - $margin;
$offset = - $stepSize / 2 + $margin / 2;
if ( $barWidth < 0 )
{
$offset -= $barWidth = abs( $barWidth );
}
$startDepth = $this->options->barMargin;
$midDepth = .5;
$endDepth = 1 - $this->options->barMargin;
switch ( $symbol )
{
case ezcGraph::NO_SYMBOL:
case ezcGraph::DIAMOND:
case ezcGraph::BULLET:
case ezcGraph::CIRCLE:
$this->drawRectangularBar(
$context,
$color,
$position,
$barWidth,
$offset,
$start->y,
$startDepth,
$midDepth,
$endDepth
);
break;
}
}
/**
* Draw all collected bar elements
*
* Draw all collected bar elements after sorting them depending of their
* position to simulate simple z buffering.
*
* @access protected
* @return void
*/
protected function finishBars()
{
if ( !count( $this->barPostProcessing ) )
{
return true;
}
$zIndexArray = array();
foreach ( $this->barPostProcessing as $key => $barPolygon )
{
$zIndexArray[$key] = $barPolygon['index'];
}
array_multisort(
$zIndexArray, SORT_ASC, SORT_NUMERIC,
$this->barPostProcessing
);
foreach ( $this->barPostProcessing as $bar )
{
$this->addElementReference( $bar['context'],
call_user_func_array(
array( $this->driver, $bar['method'] ),
$bar['parameters']
)
);
}
}
/**
* Draw data line
*
* Draws a line as a data element in a line chart
*
* @param ezcGraphBoundings $boundings Chart boundings
* @param ezcGraphContext $context Context of call
* @param ezcGraphColor $color Color of line
* @param ezcGraphCoordinate $start Starting point
* @param ezcGraphCoordinate $end Ending point
* @param int $dataNumber Number of dataset
* @param int $dataCount Count of datasets in chart
* @param int $symbol Symbol to draw for line
* @param ezcGraphColor $symbolColor Color of the symbol, defaults to linecolor
* @param ezcGraphColor $fillColor Color to fill line with
* @param float $axisPosition Position of axis for drawing filled lines
* @param float $thickness Line thickness
* @return void
*/
public function drawDataLine(
ezcGraphBoundings $boundings,
ezcGraphContext $context,
ezcGraphColor $color,
ezcGraphCoordinate $start,
ezcGraphCoordinate $end,
$dataNumber = 0,
$dataCount = 1,
$symbol = ezcGraph::NO_SYMBOL,
ezcGraphColor $symbolColor = null,
ezcGraphColor $fillColor = null,
$axisPosition = 0.,
$thickness = 1. )
{
// Calculate line width based on options
if ( $this->options->seperateLines )
{
$startDepth = ( 1 / $dataCount ) * $dataNumber;
$endDepth = ( 1 / $dataCount ) * ( $dataNumber + 1 );
}
else
{
$startDepth = false;
$endDepth = true;
}
// Determine Coordinates depending on boundings and data point position
$startCoord = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $start->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $start->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
$endCoord = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $end->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
// 3D-fy coordinates
$linePolygonPoints = array(
$this->get3dCoordinate( $startCoord, $startDepth ),
$this->get3dCoordinate( $endCoord, $startDepth ),
$this->get3dCoordinate( $endCoord, $endDepth ),
$this->get3dCoordinate( $startCoord, $endDepth ),
);
$startAxisCoord = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $start->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
$endAxisCoord = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
// 3D-fy coordinates
$axisPolygonPoints = array(
$this->get3dCoordinate( $startAxisCoord, $startDepth ),
$this->get3dCoordinate( $endAxisCoord, $startDepth ),
$this->get3dCoordinate( $endAxisCoord, $endDepth ),
$this->get3dCoordinate( $startAxisCoord, $endDepth ),
);
// Perhaps fill up line
if ( $fillColor !== null &&
$start->x != $end->x )
{
$startValue = $axisPosition - $start->y;
$endValue = $axisPosition - $end->y;
if ( ( $startValue == 0 ) ||
( $endValue == 0 ) ||
( $startValue / abs( $startValue ) == $endValue / abs( $endValue ) ) )
{
// Values have the same sign or are on the axis
$this->driver->drawPolygon(
array(
$linePolygonPoints[0],
$linePolygonPoints[1],
$this->get3dCoordinate( $endAxisCoord, $startDepth ),
$this->get3dCoordinate( $startAxisCoord, $startDepth ),
),
$fillColor,
true
);
}
else
{
// values are on differente sides of the axis - split the filled polygon
$startDiff = abs( $axisPosition - $start->y );
$endDiff = abs( $axisPosition - $end->y );
$cuttingPosition = $startDiff / ( $endDiff + $startDiff );
$cuttingPoint = new ezcGraphCoordinate(
$startCoord->x + ( $endCoord->x - $startCoord->x ) * $cuttingPosition,
$startAxisCoord->y
);
$this->driver->drawPolygon(
array(
$this->get3dCoordinate( $startAxisCoord, $startDepth ),
$linePolygonPoints[0],
$this->get3dCoordinate( $cuttingPoint, $startDepth ),
),
$fillColor,
true
);
$this->driver->drawPolygon(
array(
$this->get3dCoordinate( $endAxisCoord, $startDepth ),
$linePolygonPoints[1],
$this->get3dCoordinate( $cuttingPoint, $startDepth ),
),
$fillColor,
true
);
}
// Draw closing foo
$this->driver->drawPolygon(
array(
$linePolygonPoints[2],
$linePolygonPoints[1],
$this->get3dCoordinate( $endAxisCoord, $startDepth ),
$this->get3dCoordinate( $endAxisCoord, $endDepth ),
),
$fillColor,
true
);
}
// Draw line
$this->driver->drawPolygon(
$linePolygonPoints,
$color,
true,
$thickness
);
// Draw polygon border
if ( $this->options->dataBorder > 0 )
{
$this->driver->drawPolygon(
$linePolygonPoints,
$color->darken( $this->options->dataBorder ),
false,
$thickness
);
}
// Draw line symbol
if ( $this->options->showSymbol &&
( $symbol !== ezcGraph::NO_SYMBOL ) )
{
if ( $symbolColor === null )
{
$symbolColor = $color;
}
$this->linePostSymbols[] = array(
'boundings' => new ezcGraphBoundings(
$linePolygonPoints[2]->x - $this->options->symbolSize / 2,
$linePolygonPoints[2]->y - $this->options->symbolSize / 2,
$linePolygonPoints[2]->x + $this->options->symbolSize / 2,
$linePolygonPoints[2]->y + $this->options->symbolSize / 2
),
'color' => $symbolColor,
'context' => $context,
'symbol' => $symbol,
);
}
}
/**
* Draws a highlight textbox for a datapoint.
*
* A highlight textbox for line and bar charts means a box with the current
* value in the graph.
*
* @param ezcGraphBoundings $boundings Chart boundings
* @param ezcGraphContext $context Context of call
* @param ezcGraphCoordinate $end Ending point
* @param float $axisPosition Position of axis for drawing filled lines
* @param int $dataNumber Number of dataset
* @param int $dataCount Count of datasets in chart
* @param ezcGraphFontOptions $font Font used for highlight string
* @param string $text Acutual value
* @param int $size Size of highlight text
* @param ezcGraphColor $markLines
* @param int $xOffset
* @param int $yOffset
* @param float $stepSize
* @param int $type
* @return void
*/
public function drawDataHighlightText(
ezcGraphBoundings $boundings,
ezcGraphContext $context,
ezcGraphCoordinate $end,
$axisPosition = 0.,
$dataNumber = 1,
$dataCount = 1,
ezcGraphFontOptions $font,
$text,
$size,
ezcGraphColor $markLines = null,
$xOffset = 0,
$yOffset = 0,
$stepSize = 0.,
$type = ezcGraph::LINE )
{
$this->driver->options->font = $font;
$width = $this->dataBoundings->width / $dataCount;
// Calculate line width based on options
if ( $this->options->seperateLines )
{
$endDepth = ( 1 / $dataCount ) * ( $dataNumber + 1 );
}
else
{
$endDepth = true;
}
$dataPoint = new ezcGraphCoordinate(
$this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ),
$this->dataBoundings->y0 + $this->yAxisSpace + $end->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) )
);
if ( $end->y < $axisPosition )
{
$this->driver->drawTextBox(
$text,
$this->get3dCoordinate( new ezcGraphCoordinate(
$dataPoint->x - $width / 2,
$dataPoint->y - $size - $font->padding - $this->options->symbolSize
), $endDepth ),
$width * $this->xDepthFactor,
$size,
ezcGraph::CENTER | ezcGraph::BOTTOM
);
}
else
{
$this->driver->drawTextBox(
$text,
$this->get3dCoordinate( new ezcGraphCoordinate(
$dataPoint->x - $width / 2,
$dataPoint->y + $font->padding + $this->options->symbolSize
), $endDepth ),
$width * $this->xDepthFactor,
$size,
ezcGraph::CENTER | ezcGraph::TOP
);
}
}
/**
* Draw legend
*
* Will draw a legend in the bounding box
*
* @param ezcGraphBoundings $boundings Bounding of legend
* @param ezcGraphChartElementLegend $legend Legend to draw;
* @param int $type Type of legend: Protrait or landscape
* @return void
*/
public function drawLegend(
ezcGraphBoundings $boundings,
ezcGraphChartElementLegend $legend,
$type = ezcGraph::VERTICAL )
{
$labels = $legend->labels;
// Calculate boundings of each label
if ( $type & ezcGraph::VERTICAL )
{
$labelWidth = $boundings->x1 - $boundings->x0;
$labelHeight = min(
( $boundings->y1 - $boundings->y0 ) / count( $labels ) - $legend->spacing,
$legend->symbolSize + 2 * $legend->padding
);
}
else
{
$labelWidth = ( $boundings->x1 - $boundings->x0 ) / count( $labels ) - $legend->spacing;
$labelHeight = min(
$boundings->height,
$legend->symbolSize + 2 * $legend->padding
);
}
$symbolSize = $labelHeight - 2 * $legend->padding;
// Draw all labels
$labelPosition = new ezcGraphCoordinate( $boundings->x0, $boundings->y0 );
foreach ( $labels as $label )
{
$this->elements['legend_url'][$label['label']] = $label['url'];
$this->elements['legend'][$label['label']]['symbol'] = $this->drawSymbol(
new ezcGraphBoundings(
$labelPosition->x + $legend->padding,
$labelPosition->y + $legend->padding,
$labelPosition->x + $legend->padding + $symbolSize,
$labelPosition->y + $legend->padding + $symbolSize
),
$label['color'],
$label['symbol']
);
$this->elements['legend'][$label['label']]['text'] = $this->driver->drawTextBox(
$label['label'],
new ezcGraphCoordinate(
$labelPosition->x + 2 * $legend->padding + $symbolSize,
$labelPosition->y + $legend->padding
),
$labelWidth - $symbolSize - 3 * $legend->padding,
$labelHeight - 2 * $legend->padding,
ezcGraph::LEFT | ezcGraph::MIDDLE
);
$labelPosition->x += ( $type === ezcGraph::VERTICAL ? 0 : $labelWidth + $legend->spacing );
$labelPosition->y += ( $type === ezcGraph::VERTICAL ? $labelHeight + $legend->spacing : 0 );
}
}
/**
* Draw box
*
* Box are wrapping each major chart element and draw border, background
* and title to each chart element.
*
* Optionally a padding and margin for each box can be defined.
*
* @param ezcGraphBoundings $boundings Boundings of the box
* @param ezcGraphColor $background Background color
* @param ezcGraphColor $borderColor Border color
* @param int $borderWidth Border width
* @param int $margin Margin
* @param int $padding Padding
* @param mixed $title Title of the box
* @param int $titleSize Size of title in the box
* @return ezcGraphBoundings Remaining inner boundings
*/
public function drawBox(
ezcGraphBoundings $boundings,
ezcGraphColor $background = null,
ezcGraphColor $borderColor = null,
$borderWidth = 0,
$margin = 0,
$padding = 0,
$title = false,
$titleSize = 16 )
{
// Apply margin
$boundings->x0 += $margin;
$boundings->y0 += $margin;
$boundings->x1 -= $margin;
$boundings->y1 -= $margin;
if ( $background instanceof ezcGraphColor )
{
// Draw box background
$this->driver->drawPolygon(
array(
new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ),
new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ),
new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ),
new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ),
),
$background,
true
);
}
if ( ( $borderColor instanceof ezcGraphColor ) &&
( $borderWidth > 0 ) )
{
// Draw border
$this->driver->drawPolygon(
array(
new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ),
new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ),
new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ),
new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ),
),
$borderColor,
false,
$borderWidth
);
// Reduce local boundings by borderWidth
$boundings->x0 += $borderWidth;
$boundings->y0 += $borderWidth;
$boundings->x1 -= $borderWidth;
$boundings->y1 -= $borderWidth;
}
// Apply padding
$boundings->x0 += $padding;
$boundings->y0 += $padding;
$boundings->x1 -= $padding;
$boundings->y1 -= $padding;
// Add box title
if ( $title !== false )
{
switch ( $this->options->titlePosition )
{
case ezcGraph::TOP:
$this->driver->drawTextBox(
$title,
new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ),
$boundings->x1 - $boundings->x0,
$titleSize,
$this->options->titleAlignement
);
$boundings->y0 += $titleSize + $padding;
$boundings->y1 -= $titleSize + $padding;
break;
case ezcGraph::BOTTOM:
$this->driver->drawTextBox(
$title,
new ezcGraphCoordinate( $boundings->x0, $boundings->y1 - $titleSize ),
$boundings->x1 - $boundings->x0,
$titleSize,
$this->options->titleAlignement
);
$boundings->y1 -= $titleSize + $padding;
break;
}
}
return $boundings;
}
/**
* Draw text
*
* Draws the provided text in the boundings
*
* @param ezcGraphBoundings $boundings Boundings of text
* @param string $text Text
* @param int $align Alignement of text
* @param ezcGraphRotation $rotation
* @return void
*/
public function drawText(
ezcGraphBoundings $boundings,
$text,
$align = ezcGraph::LEFT,
ezcGraphRotation $rotation = null )
{
if ( $this->depth === false )
{
// We are not 3d for now, wg. rendering normal text boxes like the
// title
$topleft = new ezcGraphCoordinate(
$boundings->x0,
$boundings->y0
);
$bottomright = new ezcGraphCoordinate(
$boundings->x1,
$boundings->y1
);
}
else
{
// The 3d part started
$topleft = $this->get3dCoordinate(
new ezcGraphCoordinate(
$boundings->x0,
$boundings->y0
), false
);
$bottomright = $this->get3dCoordinate(
new ezcGraphCoordinate(
$boundings->x1,
$boundings->y1
), false
);
// Also modify rotation accordingly
if ( $rotation !== null )
{
$rotation = new ezcGraphRotation(
$rotation->getRotation(),
$this->get3dCoordinate( $rotation->getCenter(), false )
);
}
}
$this->driver->drawTextBox(
$text,
$topleft,
$bottomright->x - $topleft->x,
$bottomright->y - $topleft->y,
$align,
$rotation
);
}
/**
* Draw grid line
*
* Draw line for the grid in the chart background
*
*
* @param ezcGraphCoordinate $start Start point
* @param ezcGraphCoordinate $end End point
* @param ezcGraphColor $color Color of the grid line
* @return void
*/
public function drawGridLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color )
{
$gridPolygonCoordinates = array(
$this->get3dCoordinate( $start, false ),
$this->get3dCoordinate( $end, false ),
$this->get3dCoordinate( $end, true ),
$this->get3dCoordinate( $start, true ),
);
// Draw grid polygon
if ( $this->options->fillGrid === 0 )
{
$this->driver->drawLine(
$gridPolygonCoordinates[2],
$gridPolygonCoordinates[3],
$color
);
}
else
{
if ( $this->options->fillGrid === 1 )
{
$this->driver->drawPolygon(
$gridPolygonCoordinates,
$color,
true
);
}
else
{
$this->driver->drawPolygon(
$gridPolygonCoordinates,
$color->transparent( $this->options->fillGrid ),
true
);
}
// Draw grid lines - scedule some for later to be drawn in front of
// the data
$this->frontLines[] = array(
$gridPolygonCoordinates[0],
$gridPolygonCoordinates[1],
$color,
1
);
$this->frontLines[] = array(
$gridPolygonCoordinates[1],
$gridPolygonCoordinates[2],
$color,
1
);
$this->driver->drawLine(
$gridPolygonCoordinates[2],
$gridPolygonCoordinates[3],
$color,
1
);
$this->frontLines[] = array(
$gridPolygonCoordinates[3],
$gridPolygonCoordinates[0],
$color,
1
);
}
}
/**
* Draw step line
*
* Draw a step (marker for label position) on a axis.
*
* @param ezcGraphCoordinate $start Start point
* @param ezcGraphCoordinate $end End point
* @param ezcGraphColor $color Color of the grid line
* @return void
*/
public function drawStepLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color )
{
$stepPolygonCoordinates = array(
$this->get3dCoordinate( $start, true ),
$this->get3dCoordinate( $end, true ),
$this->get3dCoordinate( $end, false ),
$this->get3dCoordinate( $start, false ),
);
// Draw step polygon
if ( ( $this->options->fillAxis > 0 ) &&
( $this->options->fillAxis < 1 ) )
{
$this->driver->drawPolygon(
$stepPolygonCoordinates,
$color->transparent( $this->options->fillAxis ),
true
);
$this->driver->drawPolygon(
$stepPolygonCoordinates,
$color,
false
);
}
else
{
$this->driver->drawPolygon(
$stepPolygonCoordinates,
$color,
! (bool) $this->options->fillAxis
);
}
}
/**
* Draw axis
*
* Draws an axis form the provided start point to the end point. A specific
* angle of the axis is not required.
*
* For the labeleing of the axis a sorted array with major steps and an
* array with minor steps is expected, which are build like this:
* array(
* array(
* 'position' => (float),
* 'label' => (string),
* )
* )
* where the label is optional.
*
* The label renderer class defines how the labels are rendered. For more
* documentation on this topic have a look at the basic label renderer
* class.
*
* Additionally it can be specified if a major and minor grid are rendered
* by defining a color for them. The axis label is used to add a caption
* for the axis.
*
* @param ezcGraphBoundings $boundings Boundings of axis
* @param ezcGraphCoordinate $start Start point of axis
* @param ezcGraphCoordinate $end Endpoint of axis
* @param ezcGraphChartElementAxis $axis Axis to render
* @param ezcGraphAxisLabelRenderer $labelClass Used label renderer
* @return void
*/
public function drawAxis(
ezcGraphBoundings $boundings,
ezcGraphCoordinate $start,
ezcGraphCoordinate $end,
ezcGraphChartElementAxis $axis,
ezcGraphAxisLabelRenderer $labelClass = null )
{
// Calculate used space for three dimensional effects
if ( $this->depth === false )
{
$this->depth = min(
( $boundings->x1 - $boundings->x0 ) * $this->options->depth,
( $boundings->y1 - $boundings->y0 ) * $this->options->depth
);
$this->xDepthFactor = 1 - $this->depth / ( $boundings->x1 - $boundings->x0 );
$this->yDepthFactor = 1 - $this->depth / ( $boundings->y1 - $boundings->y0 );
$this->dataBoundings = clone $boundings;
}
// Clone boundings to not be affected by internal mofifications
$boundings = clone $boundings;
switch ( $axis->position )
{
case ezcGraph::TOP:
case ezcGraph::BOTTOM:
$this->xAxisSpace = ( $this->dataBoundings->x1 - $this->dataBoundings->x0 ) * $axis->axisSpace;
break;
case ezcGraph::LEFT:
case ezcGraph::RIGHT:
$this->yAxisSpace = ( $this->dataBoundings->y1 - $this->dataBoundings->y0 ) * $axis->axisSpace;
break;
}
// Determine normalized direction
$direction = new ezcGraphVector(
$start->x - $end->x,
$start->y - $end->y
);
$direction->unify();
$start->x += $boundings->x0;
$start->y += $boundings->y0;
$end->x += $boundings->x0;
$end->y += $boundings->y0;
// Shorten drawn axis, if requested.
if ( ( $this->options->shortAxis === true ) &&
( ( $axis->position === ezcGraph::TOP ) ||
( $axis->position === ezcGraph::BOTTOM ) ) )
{
$axisStart = clone $start;
$axisEnd = clone $end;
$axisStart->y += $boundings->height * $axis->axisSpace *
( $axis->position === ezcGraph::TOP ? 1 : -1 );
$axisEnd->y -= $boundings->height * $axis->axisSpace *
( $axis->position === ezcGraph::TOP ? 1 : -1 );
}
elseif ( ( $this->options->shortAxis === true ) &&
( ( $axis->position === ezcGraph::LEFT ) ||
( $axis->position === ezcGraph::RIGHT ) ) )
{
$axisStart = clone $start;
$axisEnd = clone $end;
$axisStart->x += $boundings->width * $axis->axisSpace *
( $axis->position === ezcGraph::LEFT ? 1 : -1 );
$axisEnd->x -= $boundings->width * $axis->axisSpace *
( $axis->position === ezcGraph::LEFT ? 1 : -1 );
}
else
{
$axisStart = $start;
$axisEnd = $end;
}
$axisPolygonCoordinates = array(
$this->get3dCoordinate( $axisStart, true ),
$this->get3dCoordinate( $axisEnd, true ),
$this->get3dCoordinate( $axisEnd, false ),
$this->get3dCoordinate( $axisStart, false ),
);
// Draw axis
if ( ( $this->options->fillAxis > 0 ) &&
( $this->options->fillAxis < 1 ) )
{
$this->driver->drawPolygon(
$axisPolygonCoordinates,
$axis->border->transparent( $this->options->fillAxis ),
true
);
}
else
{
$this->driver->drawPolygon(
$axisPolygonCoordinates,
$axis->border,
! (bool) $this->options->fillAxis
);
}
// Draw axis lines - scedule some for later to be drawn in front of
// the data
$this->driver->drawLine(
$axisPolygonCoordinates[0],
$axisPolygonCoordinates[1],
$axis->border,
1
);
$this->frontLines[] = array(
$axisPolygonCoordinates[1],
$axisPolygonCoordinates[2],
$axis->border,
1
);
$this->frontLines[] = array(
$axisPolygonCoordinates[2],
$axisPolygonCoordinates[3],
$axis->border,
1
);
$this->frontLines[] = array(
$axisPolygonCoordinates[3],
$axisPolygonCoordinates[0],
$axis->border,
1
);
// Draw small arrowhead
$this->drawAxisArrowHead(
$axisPolygonCoordinates[1],
$direction,
max(
$axis->minArrowHeadSize,
min(
$axis->maxArrowHeadSize,
abs( ceil( ( ( $end->x - $start->x ) + ( $end->y - $start->y ) ) * $axis->axisSpace / 4 ) )
)
),
$axis->border
);
// Draw axis label
if ( $axis->label !== false )
{
$width = $this->dataBoundings->x1 - $this->dataBoundings->x0;
switch ( $axis->position )
{
case ezcGraph::TOP:
$this->driver->drawTextBox(
$axis->label,
new ezcGraphCoordinate(
$axisPolygonCoordinates[2]->x + $axis->labelMargin - $width * ( 1 - $axis->axisSpace * 2 ),
$axisPolygonCoordinates[2]->y - $axis->labelMargin - $axis->labelSize
),
$width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin,
$axis->labelSize,
ezcGraph::TOP | ezcGraph::RIGHT
);
break;
case ezcGraph::BOTTOM:
$this->driver->drawTextBox(
$axis->label,
new ezcGraphCoordinate(
$axisPolygonCoordinates[1]->x + $axis->labelMargin,
$axisPolygonCoordinates[1]->y + $axis->labelMargin
),
$width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin,
$axis->labelSize,
ezcGraph::TOP | ezcGraph::LEFT
);
break;
case ezcGraph::LEFT:
$this->driver->drawTextBox(
$axis->label,
new ezcGraphCoordinate(
$axisPolygonCoordinates[1]->x - $width,
$axisPolygonCoordinates[1]->y - $axis->labelSize - $axis->labelMargin
),
$width - $axis->labelMargin,
$axis->labelSize,
ezcGraph::BOTTOM | ezcGraph::RIGHT
);
break;
case ezcGraph::RIGHT:
$this->driver->drawTextBox(
$axis->label,
new ezcGraphCoordinate(
$axisPolygonCoordinates[1]->x,
$axisPolygonCoordinates[1]->y - $axis->labelSize - $axis->labelMargin
),
$width - $axis->labelMargin,
$axis->labelSize,
ezcGraph::BOTTOM | ezcGraph::LEFT
);
break;
}
}
// Collect axis labels and draw, when all axisSpaces are collected
$this->axisLabels[] = array(
'object' => $labelClass,
'boundings' => $boundings,
'start' => clone $start,
'end' => clone $end,
'axis' => $axis,
);
if ( $this->xAxisSpace && $this->yAxisSpace )
{
foreach ( $this->axisLabels as $axisLabel )
{
// If font should not be synchronized, use font configuration from
// each axis
if ( $this->options->syncAxisFonts === false )
{
$this->driver->options->font = $axisLabel['axis']->font;
}
switch ( $axisLabel['axis']->position )
{
case ezcGraph::RIGHT:
case ezcGraph::LEFT:
$axisLabel['start']->x += $this->xAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 );
$axisLabel['end']->x -= $this->xAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 );
break;
case ezcGraph::TOP:
case ezcGraph::BOTTOM:
$axisLabel['start']->y += $this->yAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 );
$axisLabel['end']->y -= $this->yAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 );
break;
}
$axisLabel['object']->renderLabels(
$this,
$axisLabel['boundings'],
$axisLabel['start'],
$axisLabel['end'],
$axisLabel['axis']
);
}
}
}
/**
* Draw background image
*
* Draws a background image at the defined position. If repeat is set the
* background image will be repeated like any texture.
*
* @param ezcGraphBoundings $boundings Boundings for the background image
* @param string $file Filename of background image
* @param int $position Position of background image
* @param int $repeat Type of repetition
* @return void
*/
public function drawBackgroundImage(
ezcGraphBoundings $boundings,
$file,
$position = 48, // ezcGraph::CENTER | ezcGraph::MIDDLE
$repeat = ezcGraph::NO_REPEAT )
{
$imageData = getimagesize( $file );
$imageWidth = $imageData[0];
$imageHeight = $imageData[1];
$imageWidth = min( $imageWidth, $boundings->x1 - $boundings->x0 );
$imageHeight = min( $imageHeight, $boundings->y1 - $boundings->y0 );
$imagePosition = new ezcGraphCoordinate(
$boundings->x0,
$boundings->y0
);
// Determine x position
switch ( true ) {
case ( $repeat & ezcGraph::HORIZONTAL ):
// If is repeated on this axis fall back to position zero
case ( $position & ezcGraph::LEFT ):
$imagePosition->x = $boundings->x0;
break;
case ( $position & ezcGraph::RIGHT ):
$imagePosition->x = max(
$boundings->x1 - $imageWidth,
$boundings->x0
);
break;
default:
$imagePosition->x = max(
$boundings->x0 + ( $boundings->x1 - $boundings->x0 - $imageWidth ) / 2,
$boundings->x0
);
break;
}
// Determine y position
switch ( true ) {
case ( $repeat & ezcGraph::VERTICAL ):
// If is repeated on this axis fall back to position zero
case ( $position & ezcGraph::TOP ):
$imagePosition->y = $boundings->y0;
break;
case ( $position & ezcGraph::BOTTOM ):
$imagePosition->y = max(
$boundings->y1 - $imageHeight,
$boundings->y0
);
break;
default:
$imagePosition->y = max(
$boundings->y0 + ( $boundings->y1 - $boundings->y0 - $imageHeight ) / 2,
$boundings->y0
);
break;
}
// Texturize backround based on position and repetition
$position = new ezcGraphCoordinate(
$imagePosition->x,
$imagePosition->y
);
do
{
$position->y = $imagePosition->y;
do
{
$this->driver->drawImage(
$file,
$position,
$imageWidth,
$imageHeight
);
$position->y += $imageHeight;
}
while ( ( $position->y < $boundings->y1 ) &&
( $repeat & ezcGraph::VERTICAL ) );
$position->x += $imageWidth;
}
while ( ( $position->x < $boundings->x1 ) &&
( $repeat & ezcGraph::HORIZONTAL ) );
}
/**
* Call all postprocessing functions
*
* @return void
*/
protected function finish()
{
$this->finishCirleSectors();
$this->finishPieSegmentLabels();
$this->finishBars();
$this->finishLineSymbols();
$this->finishFrontLines();
return true;
}
/**
* Reset renderer properties
*
* Reset all renderer properties, which were calculated during the
* rendering process, to offer a clean environment for rerendering.
*
* @return void
*/
protected function resetRenderer()
{
parent::resetRenderer();
// Also reset special 3D renderer options
$this->pieSegmentLabels = array(
0 => array(),
1 => array(),
);
$this->pieSegmentBoundings = false;
$this->linePostSymbols = array();
$this->frontLines = array();
$this->circleSectors = array();
$this->barPostProcessing = array();
$this->depth = false;
$this->xDepthFactor = false;
$this->yDepthFactor = false;
$this->dataBoundings = false;
$this->axisLabels = array();
}
}
?>