summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKore Nordmann <github@kore-nordmann.de>2006-06-28 13:23:00 +0000
committerKore Nordmann <github@kore-nordmann.de>2006-06-28 13:23:00 +0000
commit6ed7f3a9b1814879c8fed5f55db1ae409797f76c (patch)
treef69295bc766e04744f43a8811574c69836049dc0 /src
parentd6bae028524a0fc548080d7b8ae1d8fa38102248 (diff)
downloadzetacomponents-graph-6ed7f3a9b1814879c8fed5f55db1ae409797f76c.zip
zetacomponents-graph-6ed7f3a9b1814879c8fed5f55db1ae409797f76c.tar.gz
- Added SVG driver
Diffstat (limited to 'src')
-rw-r--r--src/driver/gd.php4
-rw-r--r--src/driver/svg.php445
-rw-r--r--src/graph_autoload.php2
-rw-r--r--src/interfaces/chart.php2
-rw-r--r--src/options/svg_driver.php10
5 files changed, 455 insertions, 8 deletions
diff --git a/src/driver/gd.php b/src/driver/gd.php
index 6aa43f3..922469d 100644
--- a/src/driver/gd.php
+++ b/src/driver/gd.php
@@ -448,13 +448,13 @@ class ezcGraphGdDriver extends ezcGraphDriver
$center->x +
( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
$center->y +
- ( ( sin( deg2rad( $startAngle ) ) * $height + $size ) / 2 )
+ ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) + $size
),
new ezcGraphCoordinate(
$center->x +
( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
$center->y +
- ( ( sin( deg2rad( $endAngle ) ) * $height + $size ) / 2 )
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) + $size
),
new ezcGraphCoordinate(
$center->x +
diff --git a/src/driver/svg.php b/src/driver/svg.php
index 90574a5..4beae78 100644
--- a/src/driver/svg.php
+++ b/src/driver/svg.php
@@ -13,12 +13,69 @@
* @package Graph
*/
-class ezcGraphSVGDriver extends ezcGraphDriver
+class ezcGraphSvgDriver extends ezcGraphDriver
{
+ /**
+ * DOM tree of the svg document
+ *
+ * @var DOMDocument
+ */
+ protected $dom;
+
+ /**
+ * DOMElement containing all svg style definitions
+ *
+ * @var DOMElement
+ */
+ protected $defs;
+
+ /**
+ * DOMElement containing all svg objects
+ *
+ * @var DOMElement
+ */
+ protected $elements;
+
+ /**
+ * List of strings to draw
+ * array ( array(
+ * 'text' => array( 'strings' ),
+ * 'options' => ezcGraphFontOptions,
+ * )
+ *
+ * @var array
+ */
+ protected $strings = array();
+
public function __construct( array $options = array() )
{
$this->options = new ezcGraphSvgDriverOptions( $options );
+
+ }
+
+ protected function createDocument()
+ {
+ if ( $this->dom === null )
+ {
+ $this->dom = new DOMDocument();
+ $svg = $this->dom->createElement( 'svg' );
+
+ $svg->setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
+ $svg->setAttribute( 'xmlns:xlink', 'http://www.w3.org/1999/xlink' );
+ $svg->setAttribute( 'width', $this->options->width );
+ $svg->setAttribute( 'height', $this->options->height );
+ $svg->setAttribute( 'version', '1.0' );
+ $svg->setAttribute( 'id', 'ezcGraph' );
+ $this->dom->appendChild( $svg );
+
+ $this->defs = $this->dom->createElement( 'defs' );
+ $this->defs = $svg->appendChild( $this->defs );
+
+ $this->elements = $this->dom->createElement( 'g' );
+ $this->elements->setAttribute( 'id', 'chart' );
+ $this->elements = $svg->appendChild( $this->elements );
+ }
}
/**
@@ -31,7 +88,53 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1 )
{
-
+ $this->createDocument();
+
+ $lastPoint = end( $points );
+ $pointString = sprintf( ' M %.4f,%.4f',
+ $lastPoint->x,
+ $lastPoint->y
+ );
+
+ foreach ( $points as $point )
+ {
+ $pointString .= sprintf( ' L %.4f,%.4f',
+ $point->x,
+ $point->y
+ );
+ }
+ $pointString .= ' z ';
+
+ $path = $this->dom->createElement( 'path' );
+ $path->setAttribute( 'd', $pointString );
+
+ if ( $filled )
+ {
+ $path->setAttribute(
+ 'style',
+ sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2f; stroke: none;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+ else
+ {
+ $path->setAttribute(
+ 'style',
+ sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2f;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ $thickness,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+
+ $this->elements->appendChild( $path );
}
/**
@@ -44,7 +147,83 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1 )
{
+ $this->createDocument();
+
+ $pointString = sprintf( ' M %.4f,%.4f L %.4f,%.4f',
+ $start->x,
+ $start->y,
+ $end->x,
+ $end->y
+ );
+
+ $path = $this->dom->createElement( 'path' );
+ $path->setAttribute( 'd', $pointString );
+ $path->setAttribute(
+ 'style',
+ sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2f;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ $thickness,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+
+ $this->elements->appendChild( $path );
+ }
+
+ protected function testFitStringInTextBox( $string, ezcGraphCoordinate $position, $width, $height, $size )
+ {
+ // Tokenize String
+ $tokens = preg_split( '/\s+/', $string );
+ $lines = array( array() );
+ $line = 0;
+ foreach ( $tokens as $token )
+ {
+ // Add token to tested line
+ $selectedLine = $lines[$line];
+ $selectedLine[] = $token;
+
+ // Assume characters have the same width as height
+ $strWidth = $size * strlen( implode( ' ', $selectedLine ) ) * $this->options->assumedCharacterWidth;
+
+ // Check if line is too long
+ if ( $strWidth > $width )
+ {
+ if ( count( $selectedLine ) == 1 )
+ {
+ // Return false if one single word does not fit into one line
+ return false;
+ }
+ else
+ {
+ // Put word in next line instead and reduce available height by used space
+ $lines[++$line][] = $token;
+ $height -= $size * ( 1 + $this->options->lineSpacing );
+ }
+ }
+ else
+ {
+ // Everything is ok - put token in this line
+ $lines[$line][] = $token;
+ }
+
+ // Return false if text exceeds vertical limit
+ if ( $size > $height )
+ {
+ return false;
+ }
+ }
+
+ // Check width of last line
+ $strWidth = $size * strlen( implode( ' ', $selectedLine ) ) * $this->options->assumedCharacterWidth;
+ if ( $strWidth > $width ) {
+ return false;
+ }
+
+ // It seems to fit - return line array
+ return $lines;
}
/**
@@ -59,7 +238,107 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align )
{
-
+ // 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 ( $result !== false )
+ {
+ break;
+ }
+ }
+
+ if ( is_array( $result ) )
+ {
+ $this->options->font->minimalUsedFont = $size;
+
+ $this->strings[] = array(
+ 'text' => $result,
+ 'position' => $position,
+ 'width' => $width,
+ 'height' => $height,
+ 'align' => $align,
+ 'options' => $this->options->font,
+ );
+ }
+ }
+
+ protected function drawAllTexts()
+ {
+ foreach ( $this->strings as $text )
+ {
+ $size = $text['options']->minimalUsedFont;
+ $font = $text['options']->font;
+
+ $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;
+ }
+
+ // Render text with evaluated font size
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $boundings = imagettfbbox( $size, 0, $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'] - $size * strlen( $string ) * $this->options->assumedCharacterWidth ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( ( $text['width'] - $size * strlen( $string ) * $this->options->assumedCharacterWidth ) / 2 ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ }
+
+ $textNode = $this->dom->createElement( 'text', $string );
+ $textNode->setAttribute( 'x', $position->x );
+ $textNode->setAttribute( 'text-length', ( $size * strlen( $string ) * $this->options->assumedCharacterWidth ) . 'px' );
+ $textNode->setAttribute( 'y', $position->y );
+ $textNode->setAttribute(
+ 'style',
+ sprintf(
+ 'font-size: %dpx; font-family: sans-serif; fill: #%02x%02x%02x; fill-opacity: %.2f; stroke: none;',
+ $size,
+ $text['options']->color->red,
+ $text['options']->color->green,
+ $text['options']->color->blue,
+ 1 - ( $text['options']->color->alpha / 255 )
+ )
+ );
+
+ $this->elements->appendChild( $textNode );
+
+ $text['position']->y += $size * $this->options->lineSpacing;
+ }
+ }
}
/**
@@ -75,7 +354,67 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
{
+ $this->createDocument();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+ // We need the radius
+ $width /= 2;
+ $height /= 2;
+
+ $Xstart = $center->x + $width * cos( ( -$startAngle / 180 ) * M_PI );
+ $Ystart = $center->y + $height * sin( ( $startAngle / 180 ) * M_PI );
+ $Xend = $center->x + $width * cos( ( -( $endAngle ) / 180 ) * M_PI );
+ $Yend = $center->y + $height * sin( ( ( $endAngle ) / 180 ) * M_PI );
+
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute('d', sprintf('M %.2f,%.2f L %.2f,%.2f A %.2f,%2f 0 %d,1 %.2f,%.2f z',
+ // Middle
+ $center->x, $center->y,
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+
+ if ( $filled )
+ {
+ $arc->setAttribute(
+ 'style',
+ sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2f; stroke: none;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+ else
+ {
+ $arc->setAttribute(
+ 'style',
+ sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2f;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1, // Line Thickness
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+
+ $this->elements->appendChild( $arc );
}
/**
@@ -92,7 +431,60 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color )
{
+ $this->createDocument();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+ // We need the radius
+ $width /= 2;
+ $height /= 2;
+
+ $Xstart = $center->x + $width * cos( ( -$startAngle / 180 ) * M_PI );
+ $Ystart = $center->y + $height * sin( ( $startAngle / 180 ) * M_PI );
+ $Xend = $center->x + $width * cos( ( -( $endAngle ) / 180 ) * M_PI );
+ $Yend = $center->y + $height * sin( ( ( $endAngle ) / 180 ) * M_PI );
+
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute('d', sprintf(' M %.2f,%.2f
+ A %.2f,%2f 0 %d,0 %.2f,%.2f
+ L %.2f,%.2f
+ A %.2f,%2f 0 %d,1 %.2f,%.2f z',
+ // Endpoint low
+ $Xend, $Yend + $size,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Startpoint low
+ $Xstart, $Ystart + $size,
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+
+ $arc->setAttribute(
+ 'style',
+ sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2f; stroke: none;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+
+ $this->elements->appendChild( $arc );
}
/**
@@ -108,7 +500,41 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
{
+ $this->createDocument();
+ $ellipse = $this->dom->createElement('ellipse');
+ $ellipse->setAttribute( 'cx', $center->x );
+ $ellipse->setAttribute( 'cy', $center->y );
+ $ellipse->setAttribute( 'rx', $width / 2 );
+ $ellipse->setAttribute( 'ry', $height / 2 );
+
+ if ( $filled )
+ {
+ $ellipse->setAttribute(
+ 'style',
+ sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2f; stroke: none;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+ else
+ {
+ $ellipse->setAttribute(
+ 'style',
+ sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2f;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1, // Line Thickness
+ 1 - ( $color->alpha / 255 )
+ )
+ );
+ }
+
+ $this->elements->appendChild( $ellipse );
}
/**
@@ -122,7 +548,16 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
{
+ $this->createDocument();
+ $image = $this->dom->createElement( 'image' );
+ $image->setAttribute( 'x', $position->x );
+ $image->setAttribute( 'y', $position->y );
+ $image->setAttribute( 'width', $width . 'px' );
+ $image->setAttribute( 'height', $height . 'px' );
+ $image->setAttribute( 'xlink:href', $file );
+
+ $this->elements->appendChild( $image );
}
/**
@@ -133,7 +568,9 @@ class ezcGraphSVGDriver extends ezcGraphDriver
*/
public function render ( $file )
{
-
+ $this->createDocument();
+ $this->drawAllTexts();
+ $this->dom->save( $file );
}
}
diff --git a/src/graph_autoload.php b/src/graph_autoload.php
index 912f18a..349a02a 100644
--- a/src/graph_autoload.php
+++ b/src/graph_autoload.php
@@ -36,7 +36,7 @@ return array(
'ezcGraphGdDriverOptions' => 'Graph/options/gd_driver.php',
'ezcGraphGdDriverInvalidFontException' => 'Graph/exceptions/invalid_font.php',
'ezcGraphGdDriverUnsupportedImageTypeException' => 'Graph/exceptions/unsupported_image_type.php',
- 'ezcGraphSVGDriver' => 'Graph/driver/svg.php',
+ 'ezcGraphSvgDriver' => 'Graph/driver/svg.php',
'ezcGraphSvgDriverOptions' => 'Graph/options/svg_driver.php',
'ezcGraphInvalidDriverException' => 'Graph/exceptions/invalid_driver.php',
diff --git a/src/interfaces/chart.php b/src/interfaces/chart.php
index 6cc1e8c..18e1849 100644
--- a/src/interfaces/chart.php
+++ b/src/interfaces/chart.php
@@ -78,7 +78,7 @@ abstract class ezcGraphChart implements ArrayAccess
$this->elements['legend']->position = ezcGraph::LEFT;
// Define standard renderer and driver
- $this->driver = new ezcGraphSVGDriver();
+ $this->driver = new ezcGraphSvgDriver();
$this->renderer = new ezcGraphRenderer2D();
$this->renderer->setDriver( $this->driver );
}
diff --git a/src/options/svg_driver.php b/src/options/svg_driver.php
index 27e362b..b07664e 100644
--- a/src/options/svg_driver.php
+++ b/src/options/svg_driver.php
@@ -16,6 +16,13 @@ class ezcGraphSvgDriverOptions extends ezcGraphDriverOptions
{
/**
+ * Assumed percentual average width of chars with the used font
+ *
+ * @var float
+ */
+ protected $assumedCharacterWidth = .55;
+
+ /**
* Set an option value
*
* @param string $propertyName
@@ -28,6 +35,9 @@ class ezcGraphSvgDriverOptions extends ezcGraphDriverOptions
{
switch ( $propertyName )
{
+ case 'assumedCharacterWidth':
+ $this->assumedCharacterWidth = min( 1, max( 0, (float) $propertyValue ) );
+ break;
default:
parent::__set( $propertyName, $propertyValue );
break;
OpenPOWER on IntegriCloud