1237 lines
42 KiB
PHP
1237 lines
42 KiB
PHP
<?php
|
|
/**
|
|
* File containing the ezcGraphGdDriver class
|
|
*
|
|
* @package Graph
|
|
* @version 1.4.3
|
|
* @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
|
|
* @license http://ez.no/licenses/new_bsd New BSD License
|
|
*/
|
|
/**
|
|
* Driver using PHPs ext/gd to draw images. The GD extension is available on
|
|
* nearly all PHP installations, but slow and produces slightly incorrect
|
|
* results.
|
|
*
|
|
* The driver can make use of the different font extensions available with
|
|
* ext/gd. It is possible to use Free Type 2, native TTF and PostScript Type 1
|
|
* fonts.
|
|
*
|
|
* The options of this driver are configured in {@link ezcGraphGdDriverOptions}
|
|
* extending the basic driver options class {@link ezcGraphDriverOptions}.
|
|
*
|
|
* <code>
|
|
* $graph = new ezcGraphPieChart();
|
|
* $graph->palette = new ezcGraphPaletteEzGreen();
|
|
* $graph->title = 'Access statistics';
|
|
* $graph->legend = false;
|
|
*
|
|
* $graph->driver = new ezcGraphGdDriver();
|
|
* $graph->options->font = 'tutorial_font.ttf';
|
|
*
|
|
* // Generate a Jpeg with lower quality. The default settings result in a image
|
|
* // with better quality.
|
|
* //
|
|
* // The reduction of the supersampling to 1 will result in no anti aliasing of
|
|
* // the image. JPEG is not the optimal format for grapics, PNG is far better for
|
|
* // this kind of images.
|
|
* $graph->driver->options->supersampling = 1;
|
|
* $graph->driver->options->jpegQuality = 100;
|
|
* $graph->driver->options->imageFormat = IMG_JPEG;
|
|
*
|
|
* $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
|
|
* 'Mozilla' => 19113,
|
|
* 'Explorer' => 10917,
|
|
* 'Opera' => 1464,
|
|
* 'Safari' => 652,
|
|
* 'Konqueror' => 474,
|
|
* ) );
|
|
*
|
|
* $graph->render( 400, 200, 'tutorial_dirver_gd.jpg' );
|
|
* </code>
|
|
*
|
|
* @version 1.4.3
|
|
* @package Graph
|
|
* @mainclass
|
|
*/
|
|
class ezcGraphGdDriver extends ezcGraphDriver
|
|
{
|
|
|
|
/**
|
|
* Image resource
|
|
*
|
|
* @var resource
|
|
*/
|
|
protected $image;
|
|
|
|
/**
|
|
* Array with image files to draw
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $preProcessImages = array();
|
|
|
|
/**
|
|
* List of strings to draw
|
|
* array ( array(
|
|
* 'text' => array( 'strings' ),
|
|
* 'options' => ezcGraphFontOptions,
|
|
* )
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $strings = array();
|
|
|
|
/**
|
|
* Contains resources for already loaded ps fonts.
|
|
* array(
|
|
* path => resource
|
|
* )
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $psFontResources = array();
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param array $options Default option array
|
|
* @return void
|
|
* @ignore
|
|
*/
|
|
public function __construct( array $options = array() )
|
|
{
|
|
ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'gd' );
|
|
$this->options = new ezcGraphGdDriverOptions( $options );
|
|
}
|
|
|
|
/**
|
|
* Returns the image resource to draw on.
|
|
*
|
|
* If no resource exists the image will be created. The size of the
|
|
* returned image depends on the supersampling factor and the size of the
|
|
* chart.
|
|
*
|
|
* @return resource
|
|
*/
|
|
protected function getImage()
|
|
{
|
|
if ( !isset( $this->image ) )
|
|
{
|
|
$this->image = imagecreatetruecolor(
|
|
$this->supersample( $this->options->width ),
|
|
$this->supersample( $this->options->height )
|
|
);
|
|
|
|
// Default to a transparent white background
|
|
$bgColor = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 );
|
|
imagealphablending( $this->image, true );
|
|
imagesavealpha( $this->image, true );
|
|
imagefill( $this->image, 1, 1, $bgColor );
|
|
|
|
imagesetthickness(
|
|
$this->image,
|
|
$this->options->supersampling
|
|
);
|
|
}
|
|
|
|
return $this->image;
|
|
}
|
|
|
|
/**
|
|
* Allocates a color
|
|
*
|
|
* This function tries to allocate the requested color. If the color
|
|
* already exists in the imaga it will be reused.
|
|
*
|
|
* @param ezcGraphColor $color
|
|
* @return int Color index
|
|
*/
|
|
protected function allocate( ezcGraphColor $color )
|
|
{
|
|
$image = $this->getImage();
|
|
|
|
if ( $color->alpha > 0 )
|
|
{
|
|
$fetched = imagecolorexactalpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 );
|
|
if ( $fetched < 0 )
|
|
{
|
|
$fetched = imagecolorallocatealpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 );
|
|
}
|
|
return $fetched;
|
|
}
|
|
else
|
|
{
|
|
$fetched = imagecolorexact( $image, $color->red, $color->green, $color->blue );
|
|
if ( $fetched < 0 )
|
|
{
|
|
$fetched = imagecolorallocate( $image, $color->red, $color->green, $color->blue );
|
|
}
|
|
return $fetched;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an image resource from an image file
|
|
*
|
|
* @param string $file Filename
|
|
* @return resource Image
|
|
*/
|
|
protected function imageCreateFrom( $file )
|
|
{
|
|
$data = getimagesize( $file );
|
|
|
|
switch ( $data[2] )
|
|
{
|
|
case 1:
|
|
return array(
|
|
'width' => $data[0],
|
|
'height' => $data[1],
|
|
'image' => imagecreatefromgif( $file )
|
|
);
|
|
case 2:
|
|
return array(
|
|
'width' => $data[0],
|
|
'height' => $data[1],
|
|
'image' => imagecreatefromjpeg( $file )
|
|
);
|
|
case 3:
|
|
return array(
|
|
'width' => $data[0],
|
|
'height' => $data[1],
|
|
'image' => imagecreatefrompng( $file )
|
|
);
|
|
default:
|
|
throw new ezcGraphGdDriverUnsupportedImageTypeException( $data[2] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Supersamples a single coordinate value.
|
|
*
|
|
* Applies supersampling to a single coordinate value.
|
|
*
|
|
* @param float $value Coordinate value
|
|
* @return float Supersampled coordinate value
|
|
*/
|
|
protected function supersample( $value )
|
|
{
|
|
$mod = (int) floor( $this->options->supersampling / 2 );
|
|
return $value * $this->options->supersampling - $mod;
|
|
}
|
|
|
|
/**
|
|
* Draws a single polygon.
|
|
*
|
|
* @param array $points Point array
|
|
* @param ezcGraphColor $color Polygon color
|
|
* @param mixed $filled Filled
|
|
* @param float $thickness Line thickness
|
|
* @return void
|
|
*/
|
|
public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. )
|
|
{
|
|
$image = $this->getImage();
|
|
|
|
$drawColor = $this->allocate( $color );
|
|
|
|
// Create point array
|
|
$pointCount = count( $points );
|
|
$pointArray = array();
|
|
for ( $i = 0; $i < $pointCount; ++$i )
|
|
{
|
|
$pointArray[] = $this->supersample( $points[$i]->x );
|
|
$pointArray[] = $this->supersample( $points[$i]->y );
|
|
}
|
|
|
|
// Draw polygon
|
|
if ( $filled )
|
|
{
|
|
imagefilledpolygon( $image, $pointArray, $pointCount, $drawColor );
|
|
}
|
|
else
|
|
{
|
|
imagepolygon( $image, $pointArray, $pointCount, $drawColor );
|
|
}
|
|
|
|
return $points;
|
|
}
|
|
|
|
/**
|
|
* Draws a line
|
|
*
|
|
* @param ezcGraphCoordinate $start Start point
|
|
* @param ezcGraphCoordinate $end End point
|
|
* @param ezcGraphColor $color Line color
|
|
* @param float $thickness Line thickness
|
|
* @return void
|
|
*/
|
|
public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. )
|
|
{
|
|
$image = $this->getImage();
|
|
|
|
$drawColor = $this->allocate( $color );
|
|
|
|
imagesetthickness(
|
|
$this->image,
|
|
$this->options->supersampling * $thickness
|
|
);
|
|
|
|
imageline(
|
|
$image,
|
|
$this->supersample( $start->x ),
|
|
$this->supersample( $start->y ),
|
|
$this->supersample( $end->x ),
|
|
$this->supersample( $end->y ),
|
|
$drawColor
|
|
);
|
|
|
|
imagesetthickness(
|
|
$this->image,
|
|
$this->options->supersampling
|
|
);
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Returns boundings of text depending on the available font extension
|
|
*
|
|
* @param float $size Textsize
|
|
* @param ezcGraphFontOptions $font Font
|
|
* @param string $text Text
|
|
* @return ezcGraphBoundings Boundings of text
|
|
*/
|
|
protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text )
|
|
{
|
|
switch ( $font->type )
|
|
{
|
|
case ezcGraph::PS_FONT:
|
|
if ( !isset( $this->psFontResources[$font->path] ) )
|
|
{
|
|
$this->psFontResources[$font->path] = imagePsLoadFont( $font->path );
|
|
}
|
|
|
|
$boundings = imagePsBBox( $text, $this->psFontResources[$font->path], $size );
|
|
return new ezcGraphBoundings(
|
|
$boundings[0],
|
|
$boundings[1],
|
|
$boundings[2],
|
|
$boundings[3]
|
|
);
|
|
case ezcGraph::TTF_FONT:
|
|
switch ( true )
|
|
{
|
|
case ezcBaseFeatures::hasFunction( 'imageftbbox' ) && !$this->options->forceNativeTTF:
|
|
$boundings = imageFtBBox( $size, 0, $font->path, $text );
|
|
return new ezcGraphBoundings(
|
|
$boundings[0],
|
|
$boundings[1],
|
|
$boundings[4],
|
|
$boundings[5]
|
|
);
|
|
case ezcBaseFeatures::hasFunction( 'imagettfbbox' ):
|
|
$boundings = imageTtfBBox( $size, 0, $font->path, $text );
|
|
return new ezcGraphBoundings(
|
|
$boundings[0],
|
|
$boundings[1],
|
|
$boundings[4],
|
|
$boundings[5]
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render text depending of font type and available font extensions
|
|
*
|
|
* @param resource $image Image resource
|
|
* @param string $text Text
|
|
* @param int $type Font type
|
|
* @param string $path Font path
|
|
* @param ezcGraphColor $color Font color
|
|
* @param ezcGraphCoordinate $position Position
|
|
* @param float $size Textsize
|
|
* @param ezcGraphRotation $rotation
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function renderText( $image, $text, $type, $path, ezcGraphColor $color, ezcGraphCoordinate $position, $size, ezcGraphRotation $rotation = null )
|
|
{
|
|
if ( $rotation !== null )
|
|
{
|
|
// Rotation is relative to top left point of text and not relative
|
|
// to the bounding coordinate system
|
|
$rotation = new ezcGraphRotation(
|
|
$rotation->getRotation(),
|
|
new ezcGraphCoordinate(
|
|
$rotation->getCenter()->x - $position->x,
|
|
$rotation->getCenter()->y - $position->y
|
|
)
|
|
);
|
|
}
|
|
|
|
switch ( $type )
|
|
{
|
|
case ezcGraph::PS_FONT:
|
|
imagePsText(
|
|
$image,
|
|
$text,
|
|
$this->psFontResources[$path],
|
|
$size,
|
|
$this->allocate( $color ),
|
|
1,
|
|
$position->x +
|
|
( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
|
|
$position->y +
|
|
( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
|
|
0,
|
|
0,
|
|
( $rotation === null ? 0 : -$rotation->getRotation() ),
|
|
4
|
|
);
|
|
break;
|
|
case ezcGraph::TTF_FONT:
|
|
switch ( true )
|
|
{
|
|
case ezcBaseFeatures::hasFunction( 'imagefttext' ) && !$this->options->forceNativeTTF:
|
|
imageFtText(
|
|
$image,
|
|
$size,
|
|
( $rotation === null ? 0 : -$rotation->getRotation() ),
|
|
$position->x +
|
|
( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
|
|
$position->y +
|
|
( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
|
|
$this->allocate( $color ),
|
|
$path,
|
|
$text
|
|
);
|
|
break;
|
|
case ezcBaseFeatures::hasFunction( 'imagettftext' ):
|
|
imageTtfText(
|
|
$image,
|
|
$size,
|
|
( $rotation === null ? 0 : -$rotation->getRotation() ),
|
|
$position->x +
|
|
( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
|
|
$position->y +
|
|
( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
|
|
$this->allocate( $color ),
|
|
$path,
|
|
$text
|
|
);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes text in a box of desired size
|
|
*
|
|
* @param string $string Text
|
|
* @param ezcGraphCoordinate $position Top left position
|
|
* @param float $width Width of text box
|
|
* @param float $height Height of text box
|
|
* @param int $align Alignement of text
|
|
* @param ezcGraphRotation $rotation
|
|
* @return void
|
|
*/
|
|
public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null )
|
|
{
|
|
$padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 );
|
|
|
|
$width -= $padding * 2;
|
|
$height -= $padding * 2;
|
|
$position->x += $padding;
|
|
$position->y += $padding;
|
|
|
|
// Try to get a font size for the text to fit into the box
|
|
$maxSize = min( $height, $this->options->font->maxFontSize );
|
|
$result = false;
|
|
for ( $size = $maxSize; $size >= $this->options->font->minFontSize; --$size )
|
|
{
|
|
$result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size );
|
|
if ( is_array( $result ) )
|
|
{
|
|
break;
|
|
}
|
|
$size = floor( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : $newsize );
|
|
}
|
|
|
|
if ( !is_array( $result ) )
|
|
{
|
|
if ( ( $height >= $this->options->font->minFontSize ) &&
|
|
( $this->options->autoShortenString ) )
|
|
{
|
|
$result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize );
|
|
}
|
|
else
|
|
{
|
|
throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height );
|
|
}
|
|
}
|
|
|
|
$this->options->font->minimalUsedFont = $size;
|
|
|
|
$this->strings[] = array(
|
|
'text' => $result,
|
|
'position' => $position,
|
|
'width' => $width,
|
|
'height' => $height,
|
|
'align' => $align,
|
|
'font' => $this->options->font,
|
|
'rotation' => $rotation,
|
|
);
|
|
|
|
return array(
|
|
clone $position,
|
|
new ezcGraphCoordinate( $position->x + $width, $position->y ),
|
|
new ezcGraphCoordinate( $position->x + $width, $position->y + $height ),
|
|
new ezcGraphCoordinate( $position->x, $position->y + $height ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Draw all collected texts
|
|
*
|
|
* The texts are collected and their maximum possible font size is
|
|
* calculated. This function finally draws the texts on the image, this
|
|
* delayed drawing has two reasons:
|
|
*
|
|
* 1) This way the text strings are always on top of the image, what
|
|
* results in better readable texts
|
|
* 2) The maximum possible font size can be calculated for a set of texts
|
|
* with the same font configuration. Strings belonging to one chart
|
|
* element normally have the same font configuration, so that all texts
|
|
* belonging to one element will have the same font size.
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function drawAllTexts()
|
|
{
|
|
$image = $this->getImage();
|
|
|
|
foreach ( $this->strings as $text )
|
|
{
|
|
$size = $text['font']->minimalUsedFont;
|
|
|
|
$completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing;
|
|
|
|
// Calculate y offset for vertical alignement
|
|
switch ( true )
|
|
{
|
|
case ( $text['align'] & ezcGraph::BOTTOM ):
|
|
$yOffset = $text['height'] - $completeHeight;
|
|
break;
|
|
case ( $text['align'] & ezcGraph::MIDDLE ):
|
|
$yOffset = ( $text['height'] - $completeHeight ) / 2;
|
|
break;
|
|
case ( $text['align'] & ezcGraph::TOP ):
|
|
default:
|
|
$yOffset = 0;
|
|
break;
|
|
}
|
|
|
|
$padding = $text['font']->padding + $text['font']->borderWidth / 2;
|
|
if ( $this->options->font->minimizeBorder === true )
|
|
{
|
|
// Calculate maximum width of text rows
|
|
$width = false;
|
|
foreach ( $text['text'] as $line )
|
|
{
|
|
$string = implode( ' ', $line );
|
|
$boundings = $this->getTextBoundings( $size, $text['font'], $string );
|
|
if ( ( $width === false) || ( $boundings->width > $width ) )
|
|
{
|
|
$width = $boundings->width;
|
|
}
|
|
}
|
|
|
|
switch ( true )
|
|
{
|
|
case ( $text['align'] & ezcGraph::LEFT ):
|
|
$xOffset = 0;
|
|
break;
|
|
case ( $text['align'] & ezcGraph::CENTER ):
|
|
$xOffset = ( $text['width'] - $width ) / 2;
|
|
break;
|
|
case ( $text['align'] & ezcGraph::RIGHT ):
|
|
$xOffset = $text['width'] - $width;
|
|
break;
|
|
}
|
|
|
|
$borderPolygonArray = array(
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x - $padding + $xOffset,
|
|
$text['position']->y - $padding + $yOffset
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x + $padding * 2 + $xOffset + $width,
|
|
$text['position']->y - $padding + $yOffset
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x + $padding * 2 + $xOffset + $width,
|
|
$text['position']->y + $padding * 2 + $yOffset + $completeHeight
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x - $padding + $xOffset,
|
|
$text['position']->y + $padding * 2 + $yOffset + $completeHeight
|
|
),
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$borderPolygonArray = array(
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x - $padding,
|
|
$text['position']->y - $padding
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x + $padding * 2 + $text['width'],
|
|
$text['position']->y - $padding
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x + $padding * 2 + $text['width'],
|
|
$text['position']->y + $padding * 2 + $text['height']
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$text['position']->x - $padding,
|
|
$text['position']->y + $padding * 2 + $text['height']
|
|
),
|
|
);
|
|
}
|
|
|
|
if ( $text['rotation'] !== null )
|
|
{
|
|
foreach ( $borderPolygonArray as $nr => $point )
|
|
{
|
|
$borderPolygonArray[$nr] = $text['rotation']->transformCoordinate( $point );
|
|
}
|
|
}
|
|
|
|
if ( $text['font']->background !== false )
|
|
{
|
|
$this->drawPolygon(
|
|
$borderPolygonArray,
|
|
$text['font']->background,
|
|
true
|
|
);
|
|
}
|
|
|
|
if ( $text['font']->border !== false )
|
|
{
|
|
$this->drawPolygon(
|
|
$borderPolygonArray,
|
|
$text['font']->border,
|
|
false,
|
|
$text['font']->borderWidth
|
|
);
|
|
}
|
|
|
|
// Render text with evaluated font size
|
|
foreach ( $text['text'] as $line )
|
|
{
|
|
$string = implode( ' ', $line );
|
|
$boundings = $this->getTextBoundings( $size, $text['font'], $string );
|
|
$text['position']->y += $size;
|
|
|
|
switch ( true )
|
|
{
|
|
case ( $text['align'] & ezcGraph::LEFT ):
|
|
$position = new ezcGraphCoordinate(
|
|
$text['position']->x,
|
|
$text['position']->y + $yOffset
|
|
);
|
|
break;
|
|
case ( $text['align'] & ezcGraph::RIGHT ):
|
|
$position = new ezcGraphCoordinate(
|
|
$text['position']->x + ( $text['width'] - $boundings->width ),
|
|
$text['position']->y + $yOffset
|
|
);
|
|
break;
|
|
case ( $text['align'] & ezcGraph::CENTER ):
|
|
$position = new ezcGraphCoordinate(
|
|
$text['position']->x + ( ( $text['width'] - $boundings->width ) / 2 ),
|
|
$text['position']->y + $yOffset
|
|
);
|
|
break;
|
|
}
|
|
|
|
// Calculate relative modification of rotation center point
|
|
if ( $text['rotation'] !== null )
|
|
{
|
|
$rotation = new ezcGraphRotation(
|
|
$text['rotation']->getRotation(),
|
|
new ezcGraphCoordinate(
|
|
$text['rotation']->getCenter()->x +
|
|
$position->x - $text['position']->x,
|
|
$text['rotation']->getCenter()->y +
|
|
$position->y - $text['position']->y
|
|
)
|
|
);
|
|
$rotation = $text['rotation'];
|
|
}
|
|
else
|
|
{
|
|
$rotation = null;
|
|
}
|
|
|
|
// Optionally draw text shadow
|
|
if ( $text['font']->textShadow === true )
|
|
{
|
|
$this->renderText(
|
|
$image,
|
|
$string,
|
|
$text['font']->type,
|
|
$text['font']->path,
|
|
$text['font']->textShadowColor,
|
|
new ezcGraphCoordinate(
|
|
$position->x + $text['font']->textShadowOffset,
|
|
$position->y + $text['font']->textShadowOffset
|
|
),
|
|
$size,
|
|
$rotation
|
|
);
|
|
}
|
|
|
|
// Finally draw text
|
|
$this->renderText(
|
|
$image,
|
|
$string,
|
|
$text['font']->type,
|
|
$text['font']->path,
|
|
$text['font']->color,
|
|
$position,
|
|
$size,
|
|
$rotation
|
|
);
|
|
|
|
$text['position']->y += $size * $this->options->lineSpacing;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a sector of cirlce
|
|
*
|
|
* @param ezcGraphCoordinate $center Center of circle
|
|
* @param mixed $width Width
|
|
* @param mixed $height Height
|
|
* @param mixed $startAngle Start angle of circle sector
|
|
* @param mixed $endAngle End angle of circle sector
|
|
* @param ezcGraphColor $color Color
|
|
* @param mixed $filled Filled
|
|
* @return void
|
|
*/
|
|
public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
|
|
{
|
|
$image = $this->getImage();
|
|
$drawColor = $this->allocate( $color );
|
|
|
|
// Normalize angles
|
|
if ( $startAngle > $endAngle )
|
|
{
|
|
$tmp = $startAngle;
|
|
$startAngle = $endAngle;
|
|
$endAngle = $tmp;
|
|
}
|
|
|
|
if ( ( $endAngle - $startAngle ) > 359.99999 )
|
|
{
|
|
return $this->drawCircle( $center, $width, $height, $color, $filled );
|
|
}
|
|
|
|
// Because of bug #45552 in PHPs ext/GD we check for a minimal distance
|
|
// on the outer border of the circle sector, and skip the drawing if
|
|
// the distance is lower then 1.
|
|
//
|
|
// See also: http://bugs.php.net/45552
|
|
$startPoint = new ezcGraphVector(
|
|
$center->x +
|
|
( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
|
|
);
|
|
if ( $startPoint->sub( new ezcGraphVector(
|
|
$center->x +
|
|
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
|
|
) )->length() < 1 )
|
|
{
|
|
// Skip this circle sector
|
|
return array();
|
|
}
|
|
|
|
if ( $filled )
|
|
{
|
|
imagefilledarc(
|
|
$image,
|
|
$this->supersample( $center->x ),
|
|
$this->supersample( $center->y ),
|
|
$this->supersample( $width ),
|
|
$this->supersample( $height ),
|
|
$startAngle,
|
|
$endAngle,
|
|
$drawColor,
|
|
IMG_ARC_PIE
|
|
);
|
|
}
|
|
else
|
|
{
|
|
imagefilledarc(
|
|
$image,
|
|
$this->supersample( $center->x ),
|
|
$this->supersample( $center->y ),
|
|
$this->supersample( $width ),
|
|
$this->supersample( $height ),
|
|
$startAngle,
|
|
$endAngle,
|
|
$drawColor,
|
|
IMG_ARC_PIE | IMG_ARC_NOFILL | IMG_ARC_EDGED
|
|
);
|
|
}
|
|
|
|
// Create polygon array to return
|
|
$polygonArray = array( $center );
|
|
for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
|
|
{
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
|
|
);
|
|
}
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
|
|
);
|
|
|
|
return $polygonArray;
|
|
}
|
|
|
|
/**
|
|
* Draws a single element of a circular arc
|
|
*
|
|
* ext/gd itself does not support something like circular arcs, so that
|
|
* this functions draws rectangular polygons as a part of circular arcs
|
|
* to interpolate them. This way it is possible to apply a linear gradient
|
|
* to the circular arc, because we draw single steps anyway.
|
|
*
|
|
* @param ezcGraphCoordinate $center Center of ellipse
|
|
* @param integer $width Width of ellipse
|
|
* @param integer $height Height of ellipse
|
|
* @param integer $size Height of border
|
|
* @param float $startAngle Starting angle of circle sector
|
|
* @param float $endAngle Ending angle of circle sector
|
|
* @param ezcGraphColor $color Color of Border
|
|
* @return void
|
|
*/
|
|
protected function drawCircularArcStep( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color )
|
|
{
|
|
$this->drawPolygon(
|
|
array(
|
|
new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) + $size
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) + $size
|
|
),
|
|
new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
|
|
),
|
|
),
|
|
$color->darken( $this->options->shadeCircularArc * ( 1 + cos ( deg2rad( $startAngle ) ) ) / 2 ),
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Draws a circular arc
|
|
*
|
|
* @param ezcGraphCoordinate $center Center of ellipse
|
|
* @param integer $width Width of ellipse
|
|
* @param integer $height Height of ellipse
|
|
* @param integer $size Height of border
|
|
* @param float $startAngle Starting angle of circle sector
|
|
* @param float $endAngle Ending angle of circle sector
|
|
* @param ezcGraphColor $color Color of Border
|
|
* @param bool $filled
|
|
* @return void
|
|
*/
|
|
public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
|
|
{
|
|
$image = $this->getImage();
|
|
$drawColor = $this->allocate( $color );
|
|
|
|
// Normalize angles
|
|
if ( $startAngle > $endAngle )
|
|
{
|
|
$tmp = $startAngle;
|
|
$startAngle = $endAngle;
|
|
$endAngle = $tmp;
|
|
}
|
|
|
|
if ( $filled === true )
|
|
{
|
|
$startIteration = ceil( $startAngle / $this->options->detail ) * $this->options->detail;
|
|
$endIteration = floor( $endAngle / $this->options->detail ) * $this->options->detail;
|
|
|
|
if ( $startAngle < $startIteration )
|
|
{
|
|
// Draw initial step
|
|
$this->drawCircularArcStep(
|
|
$center,
|
|
$width,
|
|
$height,
|
|
$size,
|
|
$startAngle,
|
|
$startIteration,
|
|
$color
|
|
);
|
|
}
|
|
|
|
// Draw all steps
|
|
for ( ; $startIteration < $endIteration; $startIteration += $this->options->detail )
|
|
{
|
|
$this->drawCircularArcStep(
|
|
$center,
|
|
$width,
|
|
$height,
|
|
$size,
|
|
$startIteration,
|
|
$startIteration + $this->options->detail,
|
|
$color
|
|
);
|
|
}
|
|
|
|
if ( $endIteration < $endAngle )
|
|
{
|
|
// Draw closing step
|
|
$this->drawCircularArcStep(
|
|
$center,
|
|
$width,
|
|
$height,
|
|
$size,
|
|
$endIteration,
|
|
$endAngle,
|
|
$color
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
imagefilledarc(
|
|
$image,
|
|
$this->supersample( $center->x ),
|
|
$this->supersample( $center->y ),
|
|
$this->supersample( $width ),
|
|
$this->supersample( $height ),
|
|
$startAngle,
|
|
$endAngle,
|
|
$drawColor,
|
|
IMG_ARC_PIE | IMG_ARC_NOFILL
|
|
);
|
|
}
|
|
|
|
// Create polygon array to return
|
|
$polygonArray = array();
|
|
for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
|
|
{
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
|
|
);
|
|
}
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
|
|
);
|
|
|
|
for ( $angle = $endAngle; $angle > $startAngle; $angle -= $this->options->imageMapResolution )
|
|
{
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $angle ) ) * $width ) / 2 ) + $size,
|
|
$center->y +
|
|
( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
|
|
);
|
|
}
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ) + $size,
|
|
$center->y +
|
|
( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
|
|
);
|
|
|
|
return $polygonArray;
|
|
}
|
|
|
|
/**
|
|
* Draw circle
|
|
*
|
|
* @param ezcGraphCoordinate $center Center of ellipse
|
|
* @param mixed $width Width of ellipse
|
|
* @param mixed $height height of ellipse
|
|
* @param ezcGraphColor $color Color
|
|
* @param mixed $filled Filled
|
|
* @return void
|
|
*/
|
|
public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
|
|
{
|
|
$image = $this->getImage();
|
|
|
|
$drawColor = $this->allocate( $color );
|
|
|
|
if ( $filled )
|
|
{
|
|
imagefilledellipse(
|
|
$image,
|
|
$this->supersample( $center->x ),
|
|
$this->supersample( $center->y ),
|
|
$this->supersample( $width ),
|
|
$this->supersample( $height ),
|
|
$drawColor
|
|
);
|
|
}
|
|
else
|
|
{
|
|
imageellipse(
|
|
$image,
|
|
$this->supersample( $center->x ),
|
|
$this->supersample( $center->y ),
|
|
$this->supersample( $width ),
|
|
$this->supersample( $height ),
|
|
$drawColor
|
|
);
|
|
}
|
|
|
|
$polygonArray = array();
|
|
for ( $angle = 0; $angle < 360; $angle += $this->options->imageMapResolution )
|
|
{
|
|
$polygonArray[] = new ezcGraphCoordinate(
|
|
$center->x +
|
|
( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
|
|
$center->y +
|
|
( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
|
|
);
|
|
}
|
|
|
|
return $polygonArray;
|
|
}
|
|
|
|
/**
|
|
* Draw an image
|
|
*
|
|
* The actual drawing of the image is delayed, to not apply supersampling
|
|
* to the image. The image will normally be resized using the gd function
|
|
* imagecopyresampled, which provides nice antialiased scaling, so that
|
|
* additional supersampling would make the image look blurred. The delayed
|
|
* images will be pre-processed, so that they are draw in the back of
|
|
* everything else.
|
|
*
|
|
* @param mixed $file Image file
|
|
* @param ezcGraphCoordinate $position Top left position
|
|
* @param mixed $width Width of image in destination image
|
|
* @param mixed $height Height of image in destination image
|
|
* @return void
|
|
*/
|
|
public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
|
|
{
|
|
$this->preProcessImages[] = array(
|
|
'file' => $file,
|
|
'position' => clone $position,
|
|
'width' => $width,
|
|
'height' => $height,
|
|
);
|
|
|
|
return array(
|
|
$position,
|
|
new ezcGraphCoordinate( $position->x + $width, $position->y ),
|
|
new ezcGraphCoordinate( $position->x + $width, $position->y + $height ),
|
|
new ezcGraphCoordinate( $position->x, $position->y + $height ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Draw all images to image resource handler
|
|
*
|
|
* @param resource $image Image to draw on
|
|
* @return resource Updated image resource
|
|
*/
|
|
protected function addImages( $image )
|
|
{
|
|
foreach ( $this->preProcessImages as $preImage )
|
|
{
|
|
$preImageData = $this->imageCreateFrom( $preImage['file'] );
|
|
call_user_func_array(
|
|
$this->options->resampleFunction,
|
|
array(
|
|
$image,
|
|
$preImageData['image'],
|
|
$preImage['position']->x, $preImage['position']->y,
|
|
0, 0,
|
|
$preImage['width'], $preImage['height'],
|
|
$preImageData['width'], $preImageData['height'],
|
|
)
|
|
);
|
|
}
|
|
|
|
return $image;
|
|
}
|
|
|
|
/**
|
|
* Return mime type for current image format
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getMimeType()
|
|
{
|
|
switch ( $this->options->imageFormat )
|
|
{
|
|
case IMG_PNG:
|
|
return 'image/png';
|
|
case IMG_JPEG:
|
|
return 'image/jpeg';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render image directly to output
|
|
*
|
|
* The method renders the image directly to the standard output. You
|
|
* normally do not want to use this function, because it makes it harder
|
|
* to proper cache the generated graphs.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function renderToOutput()
|
|
{
|
|
header( 'Content-Type: ' . $this->getMimeType() );
|
|
$this->render( null );
|
|
}
|
|
|
|
/**
|
|
* Finally save image
|
|
*
|
|
* @param string $file Destination filename
|
|
* @return void
|
|
*/
|
|
public function render( $file )
|
|
{
|
|
$destination = imagecreatetruecolor( $this->options->width, $this->options->height );
|
|
|
|
// Default to a transparent white background
|
|
$bgColor = imagecolorallocatealpha( $destination, 255, 255, 255, 127 );
|
|
imagealphablending( $destination, true );
|
|
imagesavealpha( $destination, true );
|
|
imagefill( $destination, 1, 1, $bgColor );
|
|
|
|
// Apply background if one is defined
|
|
if ( $this->options->background !== false )
|
|
{
|
|
$background = $this->imageCreateFrom( $this->options->background );
|
|
|
|
call_user_func_array(
|
|
$this->options->resampleFunction,
|
|
array(
|
|
$destination,
|
|
$background['image'],
|
|
0, 0,
|
|
0, 0,
|
|
$this->options->width, $this->options->height,
|
|
$background['width'], $background['height'],
|
|
)
|
|
);
|
|
}
|
|
|
|
// Draw all images to exclude them from supersampling
|
|
$destination = $this->addImages( $destination );
|
|
|
|
// Finally merge with graph
|
|
$image = $this->getImage();
|
|
call_user_func_array(
|
|
$this->options->resampleFunction,
|
|
array(
|
|
$destination,
|
|
$image,
|
|
0, 0,
|
|
0, 0,
|
|
$this->options->width, $this->options->height,
|
|
$this->supersample( $this->options->width ), $this->supersample( $this->options->height )
|
|
)
|
|
);
|
|
|
|
$this->image = $destination;
|
|
imagedestroy( $image );
|
|
|
|
// Draw all texts
|
|
// Reset supersampling during text rendering
|
|
$supersampling = $this->options->supersampling;
|
|
$this->options->supersampling = 1;
|
|
$this->drawAllTexts();
|
|
$this->options->supersampling = $supersampling;
|
|
|
|
$image = $this->getImage();
|
|
switch ( $this->options->imageFormat )
|
|
{
|
|
case IMG_PNG:
|
|
if ( $file === null )
|
|
{
|
|
imagepng( $image );
|
|
}
|
|
else
|
|
{
|
|
imagepng( $image, $file );
|
|
}
|
|
break;
|
|
case IMG_JPEG:
|
|
imagejpeg( $image, $file, $this->options->jpegQuality );
|
|
break;
|
|
default:
|
|
throw new ezcGraphGdDriverUnsupportedImageTypeException( $this->options->imageFormat );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get resource of rendered result
|
|
*
|
|
* Return the resource of the rendered result. You should not use this
|
|
* method before you called either renderToOutput() or render(), as the
|
|
* image may not be completely rendered until then.
|
|
*
|
|
* @return resource
|
|
*/
|
|
public function getResource()
|
|
{
|
|
return $this->image;
|
|
}
|
|
}
|
|
|
|
?>
|