diff --git a/build.xml b/build.xml index 81f5962..d3027b5 100644 --- a/build.xml +++ b/build.xml @@ -27,12 +27,19 @@ - - - - + + + + + + + + - + + + + diff --git a/properties/product.properties b/properties/product.properties index 71ea517..f919118 100644 --- a/properties/product.properties +++ b/properties/product.properties @@ -2,4 +2,4 @@ name=TUnit version=0.5.0 author="Tommy Montgomery" website=http://tommymontgomery.com/ -ezc=1.0 \ No newline at end of file +ezc=1.7 \ No newline at end of file diff --git a/src/TUnit/external/ezc/Base/base.php b/src/TUnit/external/ezc/Base/base.php new file mode 100644 index 0000000..4907f55 --- /dev/null +++ b/src/TUnit/external/ezc/Base/base.php @@ -0,0 +1,656 @@ +array) + */ + protected static $repositoryDirs = array(); + + /** + * This variable stores all the elements from the autoload arrays. When a + * new autoload file is loaded, their files are added to this array. + * + * @var array(string=>string) + */ + protected static $autoloadArray = array(); + + /** + * This variable stores all the elements from the autoload arrays for + * external repositories. When a new autoload file is loaded, their files + * are added to this array. + * + * @var array(string=>string) + */ + protected static $externalAutoloadArray = array(); + + /** + * Options for the ezcBase class. + * + * @var ezcBaseOptions + */ + static private $options; + + /** + * Associates an option object with this static class. + * + * @param ezcBaseAutoloadOptions $options + */ + static public function setOptions( ezcBaseAutoloadOptions $options ) + { + self::$options = $options; + } + + /** + * Tries to autoload the given className. If the className could be found + * this method returns true, otherwise false. + * + * This class caches the requested class names (including the ones who + * failed to load). + * + * @param string $className The name of the class that should be loaded. + * + * @return bool + */ + public static function autoload( $className ) + { + ezcBase::setPackageDir(); + + // Check whether the classname is already in the cached autoloadArray. + if ( array_key_exists( $className, ezcBase::$autoloadArray ) ) + { + // Is it registered as 'unloadable'? + if ( ezcBase::$autoloadArray[$className] == false ) + { + return false; + } + ezcBase::loadFile( ezcBase::$autoloadArray[$className] ); + + return true; + } + + // Check whether the classname is already in the cached autoloadArray + // for external repositories. + if ( array_key_exists( $className, ezcBase::$externalAutoloadArray ) ) + { + // Is it registered as 'unloadable'? + if ( ezcBase::$externalAutoloadArray[$className] == false ) + { + return false; + } + ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] ); + + return true; + } + + // Not cached, so load the autoload from the package. + // Matches the first and optionally the second 'word' from the classname. + $fileNames = array(); + if ( preg_match( "/^([a-z0-9]*)([A-Z][a-z0-9]*)([A-Z][a-z0-9]*)?/", $className, $matches ) !== false ) + { + $autoloadFile = ""; + // Try to match with both names, if available. + switch ( sizeof( $matches ) ) + { + case 4: + // check for x_y_autoload.php + $autoloadFile = strtolower( "{$matches[2]}_{$matches[3]}_autoload.php" ); + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + // break intentionally missing. + + case 3: + // check for x_autoload.php + $autoloadFile = strtolower( "{$matches[2]}_autoload.php" ); + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + + // check for autoload.php + $autoloadFile = 'autoload.php'; + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + break; + } + + // Maybe there is another autoload available. + // Register this classname as false. + ezcBase::$autoloadArray[$className] = false; + } + + $path = ezcBase::$packageDir . 'autoload/'; + $realPath = realpath( $path ); + + if ( $realPath == '' ) + { + // Can not be tested, because if this happens, then the autoload + // environment has not been set-up correctly. + trigger_error( "Couldn't find autoload directory '$path'", E_USER_ERROR ); + } + + $dirs = self::getRepositoryDirectories(); + if ( ezcBase::$options && ezcBase::$options->debug ) + { + throw new ezcBaseAutoloadException( $className, $fileNames, $dirs ); + } + + return false; + } + + /** + * Sets the current working directory to $directory. + * + * @param string $directory + */ + public static function setWorkingDirectory( $directory ) + { + self::$libraryMode = 'custom'; + self::$currentWorkingDirectory = $directory; + } + + /** + * Figures out the base path of the eZ Components installation. + * + * It stores the path that it finds in a static member variable. The path + * depends on the installation method of the eZ Components. The SVN version + * has a different path than the PEAR installed version. + */ + protected static function setPackageDir() + { + if ( ezcBase::$packageDir !== null ) + { + return; + } + + // Get the path to the components. + $baseDir = dirname( __FILE__ ); + + switch ( ezcBase::$libraryMode ) + { + case "custom": + ezcBase::$packageDir = self::$currentWorkingDirectory . '/'; + break; + case "devel": + case "tarball": + ezcBase::$packageDir = $baseDir. "/../../"; + break; + case "pear"; + ezcBase::$packageDir = $baseDir. "/../"; + break; + } + } + + /** + * Tries to load the autoload array and, if loaded correctly, includes the class. + * + * @param string $fileName Name of the autoload file. + * @param string $className Name of the class that should be autoloaded. + * @param string $prefix The prefix of the class repository. + * + * @return bool True is returned when the file is correctly loaded. + * Otherwise false is returned. + */ + protected static function requireFile( $fileName, $className, $prefix ) + { + $autoloadDir = ezcBase::$packageDir . "autoload/"; + + // We need the full path to the fileName. The method file_exists() doesn't + // automatically check the (php.ini) library paths. Therefore: + // file_exists( "ezc/autoload/$fileName" ) doesn't work. + if ( $prefix === 'ezc' && file_exists( "$autoloadDir$fileName" ) ) + { + $array = require( "$autoloadDir$fileName" ); + + if ( is_array( $array) && array_key_exists( $className, $array ) ) + { + // Add the array to the cache, and include the requested file. + ezcBase::$autoloadArray = array_merge( ezcBase::$autoloadArray, $array ); + if ( ezcBase::$options !== null && ezcBase::$options->preload && !preg_match( '/Exception$/', $className ) ) + { + foreach ( $array as $loadClassName => $file ) + { + if ( $loadClassName !== 'ezcBase' && !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false ) && !preg_match( '/Exception$/', $loadClassName ) /*&& !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false )*/ ) + { + ezcBase::loadFile( ezcBase::$autoloadArray[$loadClassName] ); + } + } + } + else + { + ezcBase::loadFile( ezcBase::$autoloadArray[$className] ); + } + return true; + } + } + + // It is not in components autoload/ dir. + // try to search in additional dirs. + foreach ( ezcBase::$repositoryDirs as $repositoryPrefix => $extraDir ) + { + if ( gettype( $repositoryPrefix ) === 'string' && $repositoryPrefix !== $prefix ) + { + continue; + } + + if ( file_exists( $extraDir['autoloadDirPath'] . '/' . $fileName ) ) + { + $array = array(); + $originalArray = require( $extraDir['autoloadDirPath'] . '/' . $fileName ); + + // Building paths. + // Resulting path to class definition file consists of: + // path to extra directory with autoload file + + // basePath provided for current extra directory + + // path to class definition file stored in autoload file. + foreach ( $originalArray as $class => $classPath ) + { + $array[$class] = $extraDir['basePath'] . '/' . $classPath; + } + + if ( is_array( $array ) && array_key_exists( $className, $array ) ) + { + // Add the array to the cache, and include the requested file. + ezcBase::$externalAutoloadArray = array_merge( ezcBase::$externalAutoloadArray, $array ); + ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] ); + return true; + } + } + } + + // Nothing found :-(. + return false; + } + + /** + * Loads, require(), the given file name. If we are in development mode, + * "/src/" is inserted into the path. + * + * @param string $file The name of the file that should be loaded. + */ + protected static function loadFile( $file ) + { + switch ( ezcBase::$libraryMode ) + { + case "devel": + case "tarball": + list( $first, $second ) = explode( '/', $file, 2 ); + $file = $first . "/src/" . $second; + break; + + case "custom": + list( $first, $second ) = explode( '/', $file, 2 ); + // Add the "src/" after the package name. + if ( $first == 'Base' || $first == 'UnitTest' ) + { + list( $first, $second ) = explode( '/', $file, 2 ); + $file = $first . "/src/" . $second; + } + else + { + list( $first, $second, $third ) = explode( '/', $file, 3 ); + $file = $first . '/' . $second . "/src/" . $third; + } + break; + + case "pear": + /* do nothing, it's already correct */ + break; + } + + if ( file_exists( ezcBase::$packageDir . $file ) ) + { + require( ezcBase::$packageDir . $file ); + } + else + { + // Can not be tested, because if this happens, then one of the + // components has a broken autoload file. + throw new ezcBaseFileNotFoundException( ezcBase::$packageDir.$file ); + } + } + + /** + * Loads, require(), the given file name from an external package. + * + * @param string $file The name of the file that should be loaded. + */ + protected static function loadExternalFile( $file ) + { + if ( file_exists( $file ) ) + { + require( $file ); + } + else + { + throw new ezcBaseFileNotFoundException( $file ); + } + } + + /** + * Checks for dependencies on PHP versions or extensions + * + * The function as called by the $component component checks for the $type + * dependency. The dependency $type is compared against the $value. The + * function aborts the script if the dependency is not matched. + * + * @param string $component + * @param int $type + * @param mixed $value + */ + public static function checkDependency( $component, $type, $value ) + { + switch ( $type ) + { + case self::DEP_PHP_EXTENSION: + if ( extension_loaded( $value ) ) + { + return; + } + else + { + // Can not be tested as it would abort the PHP script. + die( "\nThe {$component} component depends on the default PHP extension '{$value}', which is not loaded.\n" ); + } + break; + + case self::DEP_PHP_VERSION: + $phpVersion = phpversion(); + if ( version_compare( $phpVersion, $value, '>=' ) ) + { + return; + } + else + { + // Can not be tested as it would abort the PHP script. + die( "\nThe {$component} component depends on the PHP version '{$value}', but the current version is '{$phpVersion}'.\n" ); + } + break; + } + } + + /** + * Return the list of directories that contain class repositories. + * + * The path to the eZ components directory is always included in the result + * array. Each element in the returned array has the format of: + * packageDirectory => ezcBaseRepositoryDirectory + * + * @return array(string=>ezcBaseRepositoryDirectory) + */ + public static function getRepositoryDirectories() + { + $autoloadDirs = array(); + ezcBase::setPackageDir(); + $repositoryDir = self::$currentWorkingDirectory ? self::$currentWorkingDirectory : ( realpath( dirname( __FILE__ ) . '/../../' ) ); + $autoloadDirs['ezc'] = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_INTERNAL, $repositoryDir, $repositoryDir . "/autoload" ); + + foreach ( ezcBase::$repositoryDirs as $extraDirKey => $extraDirArray ) + { + $repositoryDirectory = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_EXTERNAL, realpath( $extraDirArray['basePath'] ), realpath( $extraDirArray['autoloadDirPath'] ) ); + $autoloadDirs[$extraDirKey] = $repositoryDirectory; + } + + return $autoloadDirs; + } + + /** + * Adds an additional class repository. + * + * Used for adding class repositoryies outside the eZ components to be + * loaded by the autoload system. + * + * This function takes two arguments: $basePath is the base path for the + * whole class repository and $autoloadDirPath the path where autoload + * files for this repository are found. The paths in the autoload files are + * relative to the package directory as specified by the $basePath + * argument. I.e. class definition file will be searched at location + * $basePath + path to the class definition file as stored in the autoload + * file. + * + * addClassRepository() should be called somewhere in code before external classes + * are used. + * + * Example: + * Take the following facts: + * + * + * In this case you would need to create the following files in + * "./repos/autoloads". Please note that the part before _autoload.php in + * the filename is the first part of the classname, not considering + * the all lower-case letter prefix. + * + * "my_autoload.php": + * + * 'Me/myclass1.php', + * 'erMyClass2' => 'Me/myclass2.php', + * ); + * ?> + * + * + * "your_autoload.php": + * + * 'You/yourclass1.php', + * 'erYourClass2' => 'You/yourclass2.php', + * ); + * ?> + * + * + * The directory structure for the external repository is then: + * + * ./repos/autoloads/my_autoload.php + * ./repos/autoloads/you_autoload.php + * ./repos/Me/myclass1.php + * ./repos/Me/myclass2.php + * ./repos/You/yourclass1.php + * ./repos/You/yourclass2.php + * + * + * To use this repository with the autoload mechanism you have to use the + * following code: + * + * + * + * + * @throws ezcBaseFileNotFoundException if $autoloadDirPath or $basePath do not exist. + * @param string $basePath + * @param string $autoloadDirPath + * @param string $prefix + */ + public static function addClassRepository( $basePath, $autoloadDirPath = null, $prefix = null ) + { + // check if base path exists + if ( !is_dir( $basePath ) ) + { + throw new ezcBaseFileNotFoundException( $basePath, 'base directory' ); + } + + // calculate autoload path if it wasn't given + if ( is_null( $autoloadDirPath ) ) + { + $autoloadDirPath = $basePath . '/autoload'; + } + + // check if autoload dir exists + if ( !is_dir( $autoloadDirPath ) ) + { + throw new ezcBaseFileNotFoundException( $autoloadDirPath, 'autoload directory' ); + } + + // add info to $repositoryDirs + if ( $prefix === null ) + { + $array = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath ); + + // add info to the list of extra dirs + ezcBase::$repositoryDirs[] = $array; + } + else + { + if ( array_key_exists( $prefix, ezcBase::$repositoryDirs ) ) + { + throw new ezcBaseDoubleClassRepositoryPrefixException( $prefix, $basePath, $autoloadDirPath ); + } + + // add info to the list of extra dirs, and use the prefix to identify the new repository. + ezcBase::$repositoryDirs[$prefix] = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath ); + } + } + + /** + * Returns the base path of the eZ Components installation + * + * This method returns the base path, including a trailing directory + * separator. + * + * @return string + */ + public static function getInstallationPath() + { + self::setPackageDir(); + + $path = realpath( self::$packageDir ); + if ( substr( $path, -1 ) !== DIRECTORY_SEPARATOR ) + { + $path .= DIRECTORY_SEPARATOR; + } + return $path; + } + + /** + * Sets the development mode to the one specified. + * + * @param int $runMode + */ + public static function setRunMode( $runMode ) + { + if ( !in_array( $runMode, array( ezcBase::MODE_PRODUCTION, ezcBase::MODE_DEVELOPMENT ) ) ) + { + throw new ezcBaseValueException( 'runMode', $runMode, 'ezcBase::MODE_PRODUCTION or ezcBase::MODE_DEVELOPMENT' ); + } + + self::$runMode = $runMode; + } + + /** + * Returns the current development mode. + * + * @return int + */ + public static function getRunMode() + { + return self::$runMode; + } + + /** + * Returns true when we are in development mode. + * + * @return bool + */ + public static function inDevMode() + { + return self::$runMode == ezcBase::MODE_DEVELOPMENT; + } + + /** + * Returns the installation method + * + * Possible return values are 'custom', 'devel', 'tarball' and 'pear'. Only + * 'tarball' and 'pear' are returned for user-installed versions. + * + * @return string + */ + public static function getInstallMethod() + { + return self::$libraryMode; + } +} +?> diff --git a/src/TUnit/external/ezc/Base/exceptions/autoload.php b/src/TUnit/external/ezc/Base/exceptions/autoload.php new file mode 100644 index 0000000..fba1649 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/autoload.php @@ -0,0 +1,38 @@ +autoloadPath ); + } + parent::__construct( "Could not find a class to file mapping for '{$className}'. Searched for ". implode( ', ', $files ) . " in: " . implode( ', ', $paths ) ); + } +} +?> diff --git a/src/TUnit/external/ezc/Base/exceptions/double_class_repository_prefix.php b/src/TUnit/external/ezc/Base/exceptions/double_class_repository_prefix.php new file mode 100644 index 0000000..1c33df1 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/double_class_repository_prefix.php @@ -0,0 +1,34 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/exception.php b/src/TUnit/external/ezc/Base/exceptions/exception.php new file mode 100644 index 0000000..c36164f --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/exception.php @@ -0,0 +1,43 @@ +originalMessage = $message; + + if ( php_sapi_name() == 'cli' ) + { + parent::__construct( $message ); + } + else + { + parent::__construct( htmlspecialchars( $message ) ); + } + } +} +?> diff --git a/src/TUnit/external/ezc/Base/exceptions/extension_not_found.php b/src/TUnit/external/ezc/Base/exceptions/extension_not_found.php new file mode 100644 index 0000000..4e6ff88 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/extension_not_found.php @@ -0,0 +1,38 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/file_exception.php b/src/TUnit/external/ezc/Base/exceptions/file_exception.php new file mode 100644 index 0000000..945b3d5 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/file_exception.php @@ -0,0 +1,25 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/file_io.php b/src/TUnit/external/ezc/Base/exceptions/file_io.php new file mode 100644 index 0000000..3d635a5 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/file_io.php @@ -0,0 +1,50 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/file_not_found.php b/src/TUnit/external/ezc/Base/exceptions/file_not_found.php new file mode 100644 index 0000000..9b65afc --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/file_not_found.php @@ -0,0 +1,43 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/file_permission.php b/src/TUnit/external/ezc/Base/exceptions/file_permission.php new file mode 100644 index 0000000..1bbd243 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/file_permission.php @@ -0,0 +1,63 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/functionality_not_supported.php b/src/TUnit/external/ezc/Base/exceptions/functionality_not_supported.php new file mode 100644 index 0000000..3fa617c --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/functionality_not_supported.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/init_callback_configured.php b/src/TUnit/external/ezc/Base/exceptions/init_callback_configured.php new file mode 100644 index 0000000..95f158f --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/init_callback_configured.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/invalid_callback_class.php b/src/TUnit/external/ezc/Base/exceptions/invalid_callback_class.php new file mode 100644 index 0000000..80c7095 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/invalid_callback_class.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/invalid_parent_class.php b/src/TUnit/external/ezc/Base/exceptions/invalid_parent_class.php new file mode 100644 index 0000000..3ab5346 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/invalid_parent_class.php @@ -0,0 +1,29 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/property_not_found.php b/src/TUnit/external/ezc/Base/exceptions/property_not_found.php new file mode 100644 index 0000000..d4d549f --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/property_not_found.php @@ -0,0 +1,30 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/property_permission.php b/src/TUnit/external/ezc/Base/exceptions/property_permission.php new file mode 100644 index 0000000..1ee4961 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/property_permission.php @@ -0,0 +1,42 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/setting_not_found.php b/src/TUnit/external/ezc/Base/exceptions/setting_not_found.php new file mode 100644 index 0000000..df1ebcf --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/setting_not_found.php @@ -0,0 +1,29 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/setting_value.php b/src/TUnit/external/ezc/Base/exceptions/setting_value.php new file mode 100644 index 0000000..d436f37 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/setting_value.php @@ -0,0 +1,42 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/value.php b/src/TUnit/external/ezc/Base/exceptions/value.php new file mode 100644 index 0000000..fe8498a --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/value.php @@ -0,0 +1,43 @@ + diff --git a/src/TUnit/external/ezc/Base/exceptions/whatever.php b/src/TUnit/external/ezc/Base/exceptions/whatever.php new file mode 100644 index 0000000..75568b5 --- /dev/null +++ b/src/TUnit/external/ezc/Base/exceptions/whatever.php @@ -0,0 +1,40 @@ + diff --git a/src/TUnit/external/ezc/Base/ezc_bootstrap.php b/src/TUnit/external/ezc/Base/ezc_bootstrap.php new file mode 100644 index 0000000..5d8b1b0 --- /dev/null +++ b/src/TUnit/external/ezc/Base/ezc_bootstrap.php @@ -0,0 +1,40 @@ + diff --git a/src/TUnit/external/ezc/Base/features.php b/src/TUnit/external/ezc/Base/features.php new file mode 100644 index 0000000..1047565 --- /dev/null +++ b/src/TUnit/external/ezc/Base/features.php @@ -0,0 +1,365 @@ + + * + * + * + * @package Base + * @version 1.7 + */ +class ezcBaseFeatures +{ + /** + * Used to store the path of the ImageMagick convert utility. + * + * It is initialized in the {@link getImageConvertExecutable()} function. + * + * @var string + */ + private static $imageConvert = null; + + /** + * Used to store the path of the ImageMagick identify utility. + * + * It is initialized in the {@link getImageIdentifyExecutable()} function. + * + * @var string + */ + private static $imageIdentify = null; + + /** + * Used to store the operating system. + * + * It is initialized in the {@link os()} function. + * + * @var string + */ + private static $os = null; + + /** + * Determines if hardlinks are supported. + * + * @return bool + */ + public static function supportsLink() + { + return function_exists( 'link' ); + } + + /** + * Determines if symlinks are supported. + * + * @return bool + */ + public static function supportsSymLink() + { + return function_exists( 'symlink' ); + } + + /** + * Determines if posix uids are supported. + * + * @return bool + */ + public static function supportsUserId() + { + return function_exists( 'posix_getpwuid' ); + } + + /** + * Determines if the ImageMagick convert utility is installed. + * + * @return bool + */ + public static function hasImageConvert() + { + return !is_null( self::getImageConvertExecutable() ); + } + + /** + * Returns the path to the ImageMagick convert utility. + * + * On Linux, Unix,... it will return something like: /usr/bin/convert + * On Windows it will return something like: C:\Windows\System32\convert.exe + * + * @return string + */ + public static function getImageConvertExecutable() + { + if ( !is_null( self::$imageConvert ) ) + { + return self::$imageConvert; + } + return ( self::$imageConvert = self::findExecutableInPath( 'convert' ) ); + } + + /** + * Determines if the ImageMagick identify utility is installed. + * + * @return bool + */ + public static function hasImageIdentify() + { + return !is_null( self::getImageIdentifyExecutable() ); + } + + /** + * Returns the path to the ImageMagick identify utility. + * + * On Linux, Unix,... it will return something like: /usr/bin/identify + * On Windows it will return something like: C:\Windows\System32\identify.exe + * + * @return string + */ + public static function getImageIdentifyExecutable() + { + if ( !is_null( self::$imageIdentify ) ) + { + return self::$imageIdentify; + } + return ( self::$imageIdentify = self::findExecutableInPath( 'identify' ) ); + } + + /** + * Determines if the specified extension is loaded. + * + * If $version is specified, the specified extension will be tested also + * against the version of the loaded extension. + * + * Examples: + * + * hasExtensionSupport( 'gzip' ); + * + * will return true if gzip extension is loaded. + * + * + * hasExtensionSupport( 'pdo_mysql', '1.0.2' ); + * + * will return true if pdo_mysql extension is loaded and its version is at least 1.0.2. + * + * @param string $extension + * @param string $version + * @return bool + */ + public static function hasExtensionSupport( $extension, $version = null ) + { + if ( is_null( $version ) ) + { + return extension_loaded( $extension ); + } + return extension_loaded( $extension ) && version_compare( phpversion( $extension ), $version, ">=" ) ; + } + + /** + * Determines if the specified function is available. + * + * Examples: + * + * ezcBaseFeatures::hasFunction( 'imagepstext' ); + * + * will return true if support for Type 1 fonts is available with your GD + * extension. + * + * @param string $functionName + * @return bool + */ + public static function hasFunction( $functionName ) + { + return function_exists( $functionName ); + } + + /** + * Returns if a given class exists. + * Checks for a given class name and returns if this class exists or not. + * Catches the ezcBaseAutoloadException and returns false, if it was thrown. + * + * @param string $className The class to check for. + * @param bool $autoload True to use __autoload(), otherwise false. + * @return bool True if the class exists. Otherwise false. + */ + public static function classExists( $className, $autoload = true ) + { + try + { + if ( class_exists( $className, $autoload ) ) + { + return true; + } + return false; + } + catch ( ezcBaseAutoloadException $e ) + { + return false; + } + } + + /** + * Returns the operating system on which PHP is running. + * + * This method returns a sanitized form of the OS name, example + * return values are "Windows", "Mac", "Linux" and "FreeBSD". In + * all other cases it returns the value of the internal PHP constant + * PHP_OS. + * + * @return string + */ + public static function os() + { + if ( is_null( self::$os ) ) + { + $uname = php_uname( 's' ); + if ( substr( $uname, 0, 7 ) == 'Windows' ) + { + self::$os = 'Windows'; + } + elseif ( substr( $uname, 0, 3 ) == 'Mac' ) + { + self::$os = 'Mac'; + } + elseif ( strtolower( $uname ) == 'linux' ) + { + self::$os = 'Linux'; + } + elseif ( strtolower( substr( $uname, 0, 7 ) ) == 'freebsd' ) + { + self::$os = 'FreeBSD'; + } + else + { + self::$os = PHP_OS; + } + } + return self::$os; + } + + /** + * Returns the path of the specified executable, if it can be found in the system's path. + * + * It scans the PATH enviroment variable based on the OS to find the + * $fileName. For Windows, the path is with \, not /. If $fileName is not + * found, it returns null. + * + * @todo consider using getenv( 'PATH' ) instead of $_ENV['PATH'] + * (but that won't work under IIS) + * + * @param string $fileName + * @return string + */ + public static function findExecutableInPath( $fileName ) + { + if ( array_key_exists( 'PATH', $_ENV ) ) + { + $envPath = trim( $_ENV['PATH'] ); + } + else if ( ( $envPath = getenv( 'PATH' ) ) !== false ) + { + $envPath = trim( $envPath ); + } + if ( is_string( $envPath ) && strlen( trim( $envPath ) ) == 0 ) + { + $envPath = false; + } + + switch ( self::os() ) + { + case 'Unix': + case 'FreeBSD': + case 'Mac': + case 'MacOS': + case 'Darwin': + case 'Linux': + case 'SunOS': + if ( $envPath ) + { + $dirs = explode( ':', $envPath ); + foreach ( $dirs as $dir ) + { + // The @-operator is used here mainly to avoid + // open_basedir warnings. If open_basedir (or any other + // circumstance) prevents the desired file from being + // accessed, it is fine for file_exists() to return + // false, since it is useless for use then, anyway. + if ( file_exists( "{$dir}/{$fileName}" ) ) + { + return "{$dir}/{$fileName}"; + } + } + } + // The @-operator is used here mainly to avoid open_basedir + // warnings. If open_basedir (or any other circumstance) + // prevents the desired file from being accessed, it is fine + // for file_exists() to return false, since it is useless for + // use then, anyway. + elseif ( @file_exists( "./{$fileName}" ) ) + { + return $fileName; + } + break; + case 'Windows': + if ( $envPath ) + { + $dirs = explode( ';', $envPath ); + foreach ( $dirs as $dir ) + { + // The @-operator is used here mainly to avoid + // open_basedir warnings. If open_basedir (or any other + // circumstance) prevents the desired file from being + // accessed, it is fine for file_exists() to return + // false, since it is useless for use then, anyway. + if ( @file_exists( "{$dir}\\{$fileName}.exe" ) ) + { + return "{$dir}\\{$fileName}.exe"; + } + } + } + // The @-operator is used here mainly to avoid open_basedir + // warnings. If open_basedir (or any other circumstance) + // prevents the desired file from being accessed, it is fine + // for file_exists() to return false, since it is useless for + // use then, anyway. + elseif ( @file_exists( "{$fileName}.exe" ) ) + { + return "{$fileName}.exe"; + } + break; + } + return null; + } + + /** + * Reset the cached information. + * + * @return void + * @access private + * @ignore + */ + public static function reset() + { + self::$imageIdentify = null; + self::$imageConvert = null; + self::$os = null; + } +} +?> diff --git a/src/TUnit/external/ezc/Base/file.php b/src/TUnit/external/ezc/Base/file.php new file mode 100644 index 0000000..4f11aeb --- /dev/null +++ b/src/TUnit/external/ezc/Base/file.php @@ -0,0 +1,495 @@ + + * + * + * + * @package Base + * @version 1.7 + * @mainclass + */ +class ezcBaseFile +{ + /** + * This is the callback used by findRecursive to collect data. + * + * This callback method works together with walkRecursive() and is called + * for every file/and or directory. The $context is a callback specific + * container in which data can be stored and shared between the different + * calls to the callback function. The walkRecursive() function also passes + * in the full absolute directory in $sourceDir, the filename in $fileName + * and file information (such as size, modes, types) as an array as + * returned by PHP's stat() in the $fileInfo parameter. + * + * @param ezcBaseFileFindContext $context + * @param string $sourceDir + * @param string $fileName + * @param array(stat) $fileInfo + */ + static protected function findRecursiveCallback( ezcBaseFileFindContext $context, $sourceDir, $fileName, $fileInfo ) + { + // ignore if we have a directory + if ( $fileInfo['mode'] & 0x4000 ) + { + return; + } + + // update the statistics + $context->elements[] = $sourceDir . DIRECTORY_SEPARATOR . $fileName; + $context->count++; + $context->size += $fileInfo['size']; + } + + /** + * Walks files and directories recursively on a file system + * + * This method walks over a directory and calls a callback from every file + * and directory it finds. You can use $includeFilters to include only + * specific files, and $excludeFilters to exclude certain files from being + * returned. The function will always go into subdirectories even if the + * entry would not have passed the filters. + * + * The callback is passed in the $callback parameter, and the + * $callbackContext will be send to the callback function/method as + * parameter so that you can store data in there that persists with all the + * calls and recursive calls to this method. It's up to the callback method + * to do something useful with this. The callback function's parameters are + * in order: + * + * + * + * See {@see findRecursiveCallback()} for an example of a callback function. + * + * Filters are regular expressions and are therefore required to have + * starting and ending delimiters. The Perl Compatible syntax is used as + * regular expression language. + * + * @param string $sourceDir + * @param array(string) $includeFilters + * @param array(string) $excludeFilters + * @param callback $callback + * @param mixed $callbackContext + * + * @throws ezcBaseFileNotFoundException if the $sourceDir directory is not + * a directory or does not exist. + * @throws ezcBaseFilePermissionException if the $sourceDir directory could + * not be opened for reading. + * @return array + */ + static public function walkRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), $callback, &$callbackContext ) + { + if ( !is_dir( $sourceDir ) ) + { + throw new ezcBaseFileNotFoundException( $sourceDir, 'directory' ); + } + $elements = array(); + $d = @dir( $sourceDir ); + if ( !$d ) + { + throw new ezcBaseFilePermissionException( $sourceDir, ezcBaseFileException::READ ); + } + + while ( ( $entry = $d->read() ) !== false ) + { + if ( $entry == '.' || $entry == '..' ) + { + continue; + } + + $fileInfo = @stat( $sourceDir . DIRECTORY_SEPARATOR . $entry ); + if ( !$fileInfo ) + { + $fileInfo = array( 'size' => 0, 'mode' => 0 ); + } + + if ( $fileInfo['mode'] & 0x4000 ) + { + // We need to ignore the Permission exceptions here as it can + // be normal that a directory can not be accessed. We only need + // the exception if the top directory could not be read. + try + { + call_user_func_array( $callback, array( $callbackContext, $sourceDir, $entry, $fileInfo ) ); + $subList = self::walkRecursive( $sourceDir . DIRECTORY_SEPARATOR . $entry, $includeFilters, $excludeFilters, $callback, $callbackContext ); + $elements = array_merge( $elements, $subList ); + } + catch ( ezcBaseFilePermissionException $e ) + { + } + } + else + { + // By default a file is included in the return list + $ok = true; + // Iterate over the $includeFilters and prohibit the file from + // being returned when atleast one of them does not match + foreach ( $includeFilters as $filter ) + { + if ( !preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) ) + { + $ok = false; + break; + } + } + // Iterate over the $excludeFilters and prohibit the file from + // being returns when atleast one of them matches + foreach ( $excludeFilters as $filter ) + { + if ( preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) ) + { + $ok = false; + break; + } + } + + // If everything's allright, call the callback and add the + // entry to the elements array + if ( $ok ) + { + call_user_func( $callback, $callbackContext, $sourceDir, $entry, $fileInfo ); + $elements[] = $sourceDir . DIRECTORY_SEPARATOR . $entry; + } + } + } + sort( $elements ); + return $elements; + } + + /** + * Finds files recursively on a file system + * + * With this method you can scan the file system for files. You can use + * $includeFilters to include only specific files, and $excludeFilters to + * exclude certain files from being returned. The function will always go + * into subdirectories even if the entry would not have passed the filters. + * It uses the {@see walkRecursive()} method to do the actually recursion. + * + * Filters are regular expressions and are therefore required to have + * starting and ending delimiters. The Perl Compatible syntax is used as + * regular expression language. + * + * If you pass an empty array to the $statistics argument, the function + * will in details about the number of files found into the 'count' array + * element, and the total filesize in the 'size' array element. Because this + * argument is passed by reference, you *have* to pass a variable and you + * can not pass a constant value such as "array()". + * + * @param string $sourceDir + * @param array(string) $includeFilters + * @param array(string) $excludeFilters + * @param array() $statistics + * + * @throws ezcBaseFileNotFoundException if the $sourceDir directory is not + * a directory or does not exist. + * @throws ezcBaseFilePermissionException if the $sourceDir directory could + * not be opened for reading. + * @return array + */ + static public function findRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), &$statistics = null ) + { + // init statistics array + if ( !is_array( $statistics ) || !array_key_exists( 'size', $statistics ) || !array_key_exists( 'count', $statistics ) ) + { + $statistics['size'] = 0; + $statistics['count'] = 0; + } + + // create the context, and then start walking over the array + $context = new ezcBaseFileFindContext; + self::walkRecursive( $sourceDir, $includeFilters, $excludeFilters, array( 'ezcBaseFile', 'findRecursiveCallback' ), $context ); + + // collect the statistics + $statistics['size'] = $context->size; + $statistics['count'] = $context->count; + + // return the found and pattern-matched files + sort( $context->elements ); + return $context->elements; + } + + + /** + * Removes files and directories recursively from a file system + * + * This method recursively removes the $directory and all its contents. + * You should be extremely careful with this method as it has the + * potential to erase everything that the current user has access to. + * + * @param string $directory + */ + static public function removeRecursive( $directory ) + { + $sourceDir = realpath( $directory ); + if ( !$sourceDir ) + { + throw new ezcBaseFileNotFoundException( $directory, 'directory' ); + } + $d = @dir( $sourceDir ); + if ( !$d ) + { + throw new ezcBaseFilePermissionException( $directory, ezcBaseFileException::READ ); + } + // check if we can remove the dir + $parentDir = realpath( $directory . DIRECTORY_SEPARATOR . '..' ); + if ( !is_writable( $parentDir ) ) + { + throw new ezcBaseFilePermissionException( $parentDir, ezcBaseFileException::WRITE ); + } + // loop over contents + while ( ( $entry = $d->read() ) !== false ) + { + if ( $entry == '.' || $entry == '..' ) + { + continue; + } + + if ( is_dir( $sourceDir . DIRECTORY_SEPARATOR . $entry ) ) + { + self::removeRecursive( $sourceDir . DIRECTORY_SEPARATOR . $entry ); + } + else + { + if ( @unlink( $sourceDir . DIRECTORY_SEPARATOR . $entry ) === false ) + { + throw new ezcBaseFilePermissionException( $directory . DIRECTORY_SEPARATOR . $entry, ezcBaseFileException::REMOVE ); + } + } + } + $d->close(); + rmdir( $sourceDir ); + } + + /** + * Recursively copy a file or directory. + * + * Recursively copy a file or directory in $source to the given + * destination. If a depth is given, the operation will stop, if the given + * recursion depth is reached. A depth of -1 means no limit, while a depth + * of 0 means, that only the current file or directory will be copied, + * without any recursion. + * + * You may optionally define modes used to create files and directories. + * + * @throws ezcBaseFileNotFoundException + * If the $sourceDir directory is not a directory or does not exist. + * @throws ezcBaseFilePermissionException + * If the $sourceDir directory could not be opened for reading, or the + * destination is not writeable. + * + * @param string $source + * @param string $destination + * @param int $depth + * @param int $dirMode + * @param int $fileMode + * @return void + */ + static public function copyRecursive( $source, $destination, $depth = -1, $dirMode = 0775, $fileMode = 0664 ) + { + // Check if source file exists at all. + if ( !is_file( $source ) && !is_dir( $source ) ) + { + throw new ezcBaseFileNotFoundException( $source ); + } + + // Destination file should NOT exist + if ( is_file( $destination ) || is_dir( $destination ) ) + { + throw new ezcBaseFilePermissionException( $destination, ezcBaseFileException::WRITE ); + } + + // Skip non readable files in source directory + if ( !is_readable( $source ) ) + { + return; + } + + // Copy + if ( is_dir( $source ) ) + { + mkdir( $destination ); + // To ignore umask, umask() should not be changed with + // multithreaded servers... + chmod( $destination, $dirMode ); + } + elseif ( is_file( $source ) ) + { + copy( $source, $destination ); + chmod( $destination, $fileMode ); + } + + if ( ( $depth === 0 ) || + ( !is_dir( $source ) ) ) + { + // Do not recurse (any more) + return; + } + + // Recurse + $dh = opendir( $source ); + while ( ( $file = readdir( $dh ) ) !== false ) + { + if ( ( $file === '.' ) || + ( $file === '..' ) ) + { + continue; + } + + self::copyRecursive( + $source . '/' . $file, + $destination . '/' . $file, + $depth - 1, $dirMode, $fileMode + ); + } + } + + /** + * Calculates the relative path of the file/directory '$path' to a given + * $base path. + * + * $path and $base should be fully absolute paths. This function returns the + * answer of "How do I go from $base to $path". If the $path and $base are + * the same path, the function returns '.'. This method does not touch the + * filesystem. + * + * @param string $path + * @param string $base + * @return string + */ + static public function calculateRelativePath( $path, $base ) + { + // Sanitize the paths to use the correct directory separator for the platform + $path = strtr( $path, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR ); + $base = strtr( $base, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR ); + + $base = explode( DIRECTORY_SEPARATOR, $base ); + $path = explode( DIRECTORY_SEPARATOR, $path ); + + // If the paths are the same we return + if ( $base === $path ) + { + return '.'; + } + + $result = ''; + + $pathPart = array_shift( $path ); + $basePart = array_shift( $base ); + while ( $pathPart == $basePart ) + { + $pathPart = array_shift( $path ); + $basePart = array_shift( $base ); + } + + if ( $pathPart != null ) + { + array_unshift( $path, $pathPart ); + } + if ( $basePart != null ) + { + array_unshift( $base, $basePart ); + } + + $result = str_repeat( '..' . DIRECTORY_SEPARATOR, count( $base ) ); + // prevent a trailing DIRECTORY_SEPARATOR in case there is only a .. + if ( count( $path ) == 0 ) + { + $result = substr( $result, 0, -strlen( DIRECTORY_SEPARATOR ) ); + } + $result .= join( DIRECTORY_SEPARATOR, $path ); + + return $result; + } + + /** + * Returns whether the passed $path is an absolute path, giving the current $os. + * + * With the $os parameter you can tell this function to use the semantics + * for a different operating system to determine whether a path is + * absolute. The $os argument defaults to the OS that the script is running + * on. + * + * @param string $path + * @param string $os + * @return bool + */ + public static function isAbsolutePath( $path, $os = null ) + { + if ( $os === null ) + { + $os = ezcBaseFeatures::os(); + } + + // Stream wrapper like phar can also be considered absolute paths + if ( preg_match( '(^[a-z]{3,}://)S', $path ) ) + { + return true; + } + + switch ( $os ) + { + case 'Windows': + // Sanitize the paths to use the correct directory separator for the platform + $path = strtr( $path, '\\/', '\\\\' ); + + // Absolute paths with drive letter: X:\ + if ( preg_match( '@^[A-Z]:\\\\@i', $path ) ) + { + return true; + } + + // Absolute paths with network paths: \\server\share\ + if ( preg_match( '@^\\\\\\\\[A-Z]+\\\\[^\\\\]@i', $path ) ) + { + return true; + } + break; + case 'Mac': + case 'Linux': + case 'FreeBSD': + default: + // Sanitize the paths to use the correct directory separator for the platform + $path = strtr( $path, '\\/', '//' ); + + if ( $path[0] == '/' ) + { + return true; + } + } + return false; + } +} +?> diff --git a/src/TUnit/external/ezc/Base/init.php b/src/TUnit/external/ezc/Base/init.php new file mode 100644 index 0000000..5ce2435 --- /dev/null +++ b/src/TUnit/external/ezc/Base/init.php @@ -0,0 +1,125 @@ + + * + * + * + * You will also need to configure which callback class to call. This you do + * with the ezcBaseInit::setCallback() method. The following examples sets the + * callback classname for the configuration identifier + * 'ezcInitConfigurationManager' to 'cfgConfigurationManager': + * + * + * + * + * + * The class 'cfgConfigurationManager' is required to implement the + * ezcBaseConfigurationInitializer interface, which defines only one method: + * configureObject(). An example on how to implement such a class could be: + * + * + * init( 'ezcConfigurationIniReader', 'settings', array( 'useComments' => true ) ); + * } + * } + * ?> + * + * + * Of course the implementation of this callback class is up to the application + * developer that uses the component (in this example the Configuration + * component's class ezcConfigurationManager). + * + * @package Base + * @version 1.7 + */ +class ezcBaseInit +{ + /** + * Contains the callback where the identifier is the key of the array, and the classname to callback to the value. + * + * @var array(string=>string) + */ + static private $callbackMap = array(); + + /** + * Adds the classname $callbackClassname as callback for the identifier $identifier. + * + * @param string $identifier + * @param string $callbackClassname + */ + public static function setCallback( $identifier, $callbackClassname ) + { + if ( array_key_exists( $identifier, self::$callbackMap ) ) + { + throw new ezcBaseInitCallbackConfiguredException( $identifier, self::$callbackMap[$identifier] ); + } + else + { + // Check if the passed classname actually exists + if ( !ezcBaseFeatures::classExists( $callbackClassname, true ) ) + { + throw new ezcBaseInitInvalidCallbackClassException( $callbackClassname ); + } + + // Check if the passed classname actually implements the interface. + if ( !in_array( 'ezcBaseConfigurationInitializer', class_implements( $callbackClassname ) ) ) + { + throw new ezcBaseInitInvalidCallbackClassException( $callbackClassname ); + } + + self::$callbackMap[$identifier] = $callbackClassname; + } + } + + /** + * Uses the configured callback belonging to $identifier to configure the $object. + * + * The method will return the return value of the callback method, or null + * in case there was no callback set for the specified $identifier. + * + * @param string $identifier + * @param object $object + * @return mixed + */ + public static function fetchConfig( $identifier, $object ) + { + if ( isset( self::$callbackMap[$identifier] ) ) + { + $callbackClassname = self::$callbackMap[$identifier]; + return call_user_func( array( $callbackClassname, 'configureObject' ), $object ); + } + return null; + } +} +?> diff --git a/src/TUnit/external/ezc/Base/interfaces/configuration_initializer.php b/src/TUnit/external/ezc/Base/interfaces/configuration_initializer.php new file mode 100644 index 0000000..0388027 --- /dev/null +++ b/src/TUnit/external/ezc/Base/interfaces/configuration_initializer.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Base/interfaces/persistable.php b/src/TUnit/external/ezc/Base/interfaces/persistable.php new file mode 100644 index 0000000..3da6ce8 --- /dev/null +++ b/src/TUnit/external/ezc/Base/interfaces/persistable.php @@ -0,0 +1,40 @@ +mixed) + */ + public function getState(); + + /** + * Accepts an array containing data for one or more of the class' properties. + * + * @param array $properties + */ + public function setState( array $properties ); +} +?> diff --git a/src/TUnit/external/ezc/Base/metadata.php b/src/TUnit/external/ezc/Base/metadata.php new file mode 100644 index 0000000..3962649 --- /dev/null +++ b/src/TUnit/external/ezc/Base/metadata.php @@ -0,0 +1,120 @@ +reader = new ezcBaseMetaDataTarballReader; + break; + case 'pear': + $this->reader = new ezcBaseMetaDataPearReader; + break; + default: + throw new ezcBaseMetaDataReaderException( "Unknown install method '$installMethod'." ); + break; + } + } + + /** + * Returns the version string for the installed eZ Components bundle. + * + * A version string such as "2008.2.2" is returned. + * + * @return string + */ + public function getBundleVersion() + { + return $this->reader->getBundleVersion(); + } + + /** + * Returns a PHP version string that describes the required PHP version for + * this installed eZ Components bundle. + * + * @return string + */ + public function getRequiredPhpVersion() + { + return $this->reader->getRequiredPhpVersion(); + } + + /** + * Returns whether $componentName is installed + * + * If installed with PEAR, it checks the PEAR registry whether the + * component is there. In case the tarball installation method is used, it + * will return true for every component that exists (because all of them + * are then available). + * + * @return bool + */ + public function isComponentInstalled( $componentName ) + { + return $this->reader->isComponentInstalled( $componentName ); + } + + /** + * Returns the version string of the available $componentName or false when + * the component is not installed. + * + * @return string + */ + public function getComponentVersion( $componentName ) + { + return $this->reader->getComponentVersion( $componentName ); + } + + /** + * Returns a list of components that $componentName depends on. + * + * If $componentName is left empty, all installed components are returned. + * + * The returned array has as keys the component names, and as values the + * version of the components. + * + * @return array(string=>string). + */ + public function getComponentDependencies( $componentName = null ) + { + if ( $componentName === null ) + { + return $this->reader->getComponentDependencies(); + } + else + { + return $this->reader->getComponentDependencies( $componentName ); + } + } +} +?> diff --git a/src/TUnit/external/ezc/Base/metadata/pear.php b/src/TUnit/external/ezc/Base/metadata/pear.php new file mode 100644 index 0000000..6c789d2 --- /dev/null +++ b/src/TUnit/external/ezc/Base/metadata/pear.php @@ -0,0 +1,129 @@ +registry = new PEAR_Registry; + } + + /** + * Returns the version string for the installed eZ Components bundle. + * + * A version string such as "2008.2.2" is returned. + * + * @return string + */ + public function getBundleVersion() + { + @$packageInfo = $this->registry->packageInfo( 'ezcomponents', null, 'components.ez.no' ); + return $packageInfo['version']['release']; + } + + /** + * Returns a PHP version string that describes the required PHP version for + * this installed eZ Components bundle. + * + * @return string + */ + public function getRequiredPhpVersion() + { + @$packageInfo = $this->registry->packageInfo( 'ezcomponents', null, 'components.ez.no' ); + if ( array_key_exists( 'required', $packageInfo['dependencies'] ) ) + { + return $packageInfo['dependencies']['required']['php']['min']; + } + return $packageInfo['dependencies']['php']['min']; + } + + /** + * Returns whether $componentName is installed + * + * Checks the PEAR registry whether the component is there. + * + * @return bool + */ + public function isComponentInstalled( $componentName ) + { + @$packageInfo = $this->registry->packageInfo( $componentName, null, 'components.ez.no' ); + return is_array( $packageInfo ); + } + + /** + * Returns the version string of the available $componentName or false when + * the component is not installed. + * + * @return string + */ + public function getComponentVersion( $componentName ) + { + @$packageInfo = $this->registry->packageInfo( $componentName, null, 'components.ez.no' ); + $release = $packageInfo['version']['release']; + return $release === null ? false : $release; + } + + /** + * Returns a list of components that $componentName depends on. + * + * If $componentName is left empty, all installed components are returned. + * + * The returned array has as keys the component names, and as values the + * version of the components. + * + * @return array(string=>string). + */ + public function getComponentDependencies( $componentName = 'ezcomponents' ) + { + @$packageInfo = $this->registry->packageInfo( $componentName, 'dependencies', 'components.ez.no' ); + if ( isset( $packageInfo['required']['package'] ) ) + { + $deps = array(); + if ( isset( $packageInfo['required']['package']['name'] ) ) + { + $deps[$packageInfo['required']['package']['name']] = $packageInfo['required']['package']['min']; + } + else + { + foreach ( $packageInfo['required']['package'] as $package ) + { + $deps[$package['name']] = $package['min']; + } + } + return $deps; + } + return array(); + } +} +?> diff --git a/src/TUnit/external/ezc/Base/metadata/tarball.php b/src/TUnit/external/ezc/Base/metadata/tarball.php new file mode 100644 index 0000000..5f82042 --- /dev/null +++ b/src/TUnit/external/ezc/Base/metadata/tarball.php @@ -0,0 +1,153 @@ +xml = simplexml_load_file( $filename ); + } + + /** + * Returns the version string for the installed eZ Components bundle. + * + * A version string such as "2008.2.2" is returned. + * + * @return string + */ + public function getBundleVersion() + { + return (string) $this->xml->version; + } + + /** + * Returns a PHP version string that describes the required PHP version for + * this installed eZ Components bundle. + * + * @return string + */ + public function getRequiredPhpVersion() + { + return (string) $this->xml->deps->php; + } + + /** + * Returns whether $componentName is installed + * + * Returns true for every component that exists (because all of them are + * then available). + * + * @return bool + */ + public function isComponentInstalled( $componentName ) + { + $root = $this->xml->deps->packages->package; + + foreach ( $root as $package ) + { + if ( (string) $package['name'] == $componentName ) + { + return true; + } + } + return false; + } + + /** + * Returns the version string of the available $componentName or false when + * the component is not installed. + * + * @return string + */ + public function getComponentVersion( $componentName ) + { + $root = $this->xml->deps->packages->package; + + foreach ( $root as $package ) + { + if ( (string) $package['name'] == $componentName ) + { + return (string) $package['version']; + } + } + return false; + } + + /** + * Returns a list of components that $componentName depends on. + * + * If $componentName is left empty, all installed components are returned. + * + * The returned array has as keys the component names, and as values the + * version of the components. It returns null of the $componentName + * is not found. + * + * @return array(string=>string). + */ + public function getComponentDependencies( $componentName = null ) + { + $baseVersion = false; + $root = $this->xml->deps->packages; + $found = $componentName === null ? true : false; + + // in case $componentName != null, we loop through all the components + // in the file, and figure out the new root that we can list dependency + // packages from. + foreach ( $root->package as $package ) + { + if ( (string) $package['name'] == 'Base' ) + { + $baseVersion = $package['version']; + } + if ( !$found && (string) $package['name'] == $componentName ) + { + $root = $package->deps; + $found = true; + } + } + + if ( !$found ) + { + return null; + } + + // We always add the Base dependency even though it's not in the dependency file. + $deps = array(); + $deps['Base'] = (string) $baseVersion; + + if ( !isset( $root->package ) ) + { + return $deps; + } + foreach ( $root->package as $package ) + { + $deps[(string) $package['name']] = (string) $package['version']; + } + return $deps; + } +} +?> diff --git a/src/TUnit/external/ezc/Base/options.php b/src/TUnit/external/ezc/Base/options.php new file mode 100644 index 0000000..79cc03c --- /dev/null +++ b/src/TUnit/external/ezc/Base/options.php @@ -0,0 +1,174 @@ +mixed) + */ + protected $properties; + + /** + * Construct a new options object. + * Options are constructed from an option array by default. The constructor + * automatically passes the given options to the __set() method to set them + * in the class. + * + * @throws ezcBasePropertyNotFoundException + * If trying to access a non existent property. + * @throws ezcBaseValueException + * If the value for a property is out of range. + * @param array(string=>mixed) $options The initial options to set. + */ + public function __construct( array $options = array() ) + { + foreach ( $options as $option => $value ) + { + $this->__set( $option, $value ); + } + } + + /** + * Merge an array into the actual options object. + * This method merges an array of new options into the actual options object. + * + * @throws ezcBasePropertyNotFoundException + * If trying to access a non existent property. + * @throws ezcBaseValueException + * If the value for a property is out of range. + * @param array(string=>mixed) $newOptions The new options. + */ + public function merge( array $newOptions ) + { + foreach ( $newOptions as $key => $value ) + { + $this->__set( $key, $value ); + } + } + + /** + * Property get access. + * Simply returns a given option. + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @param string $propertyName The name of the option to get. + * @return mixed The option value. + * @ignore + * + * @throws ezcBasePropertyNotFoundException + * if the given property does not exist. + * @throws ezcBasePropertyPermissionException + * if the property to be set is a write-only property. + */ + public function __get( $propertyName ) + { + if ( $this->__isset( $propertyName ) === true ) + { + return $this->properties[$propertyName]; + } + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + + /** + * Sets an option. + * This method is called when an option is set. + * + * @param string $propertyName The name of the option to set. + * @param mixed $propertyValue The option value. + * @ignore + * + * @throws ezcBasePropertyNotFoundException + * if the given property does not exist. + * @throws ezcBaseValueException + * if the value to be assigned to a property is invalid. + * @throws ezcBasePropertyPermissionException + * if the property to be set is a read-only property. + */ + abstract public function __set( $propertyName, $propertyValue ); + + /** + * Returns if a option exists. + * + * @param string $propertyName Option name to check for. + * @return bool Whether the option exists. + * @ignore + */ + public function __isset( $propertyName ) + { + return array_key_exists( $propertyName, $this->properties ); + } + + /** + * Returns if an option exists. + * Allows isset() using ArrayAccess. + * + * @param string $propertyName The name of the option to get. + * @return bool Whether the option exists. + */ + public function offsetExists( $propertyName ) + { + return $this->__isset( $propertyName ); + } + + /** + * Returns an option value. + * Get an option value by ArrayAccess. + * + * @throws ezcBasePropertyNotFoundException + * If $propertyName is not a key in the $properties array. + * @param string $propertyName The name of the option to get. + * @return mixed The option value. + */ + public function offsetGet( $propertyName ) + { + return $this->__get( $propertyName ); + } + + /** + * Set an option. + * Sets an option using ArrayAccess. + * + * @throws ezcBasePropertyNotFoundException + * If $propertyName is not a key in the $properties array. + * @throws ezcBaseValueException + * If the value for a property is out of range. + * @param string $propertyName The name of the option to set. + * @param mixed $propertyValue The value for the option. + */ + public function offsetSet( $propertyName, $propertyValue ) + { + $this->__set( $propertyName, $propertyValue ); + } + + /** + * Unset an option. + * Unsets an option using ArrayAccess. + * + * @throws ezcBasePropertyNotFoundException + * If $propertyName is not a key in the $properties array. + * @throws ezcBaseValueException + * If a the value for a property is out of range. + * @param string $propertyName The name of the option to unset. + */ + public function offsetUnset( $propertyName ) + { + $this->__set( $propertyName, null ); + } +} +?> diff --git a/src/TUnit/external/ezc/Base/options/autoload.php b/src/TUnit/external/ezc/Base/options/autoload.php new file mode 100644 index 0000000..09b758f --- /dev/null +++ b/src/TUnit/external/ezc/Base/options/autoload.php @@ -0,0 +1,75 @@ +mixed) $options + */ + public function __construct( array $options = array() ) + { + $this->preload = false; + $this->debug = false; + + parent::__construct( $options ); + } + + /** + * Sets the option $name to $value. + * + * @throws ezcBasePropertyNotFoundException + * if the property $name is not defined + * @throws ezcBaseValueException + * if $value is not correct for the property $name + * @param string $name + * @param mixed $value + * @ignore + */ + public function __set( $name, $value ) + { + switch ( $name ) + { + case 'debug': + case 'preload': + if ( !is_bool( $value ) ) + { + throw new ezcBaseValueException( $name, $value, 'bool' ); + } + $this->properties[$name] = $value; + break; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } +} +?> diff --git a/src/TUnit/external/ezc/Base/struct.php b/src/TUnit/external/ezc/Base/struct.php new file mode 100644 index 0000000..3c43f20 --- /dev/null +++ b/src/TUnit/external/ezc/Base/struct.php @@ -0,0 +1,42 @@ + diff --git a/src/TUnit/external/ezc/Base/structs/file_find_context.php b/src/TUnit/external/ezc/Base/structs/file_find_context.php new file mode 100644 index 0000000..1ce0cf1 --- /dev/null +++ b/src/TUnit/external/ezc/Base/structs/file_find_context.php @@ -0,0 +1,72 @@ +elements = $elements; + $this->count = $count; + $this->size = $size; + } + + /** + * Returns a new instance of this class with the data specified by $array. + * + * $array contains all the data members of this class in the form: + * array('member_name'=>value). + * + * __set_state makes this class exportable with var_export. + * var_export() generates code, that calls this method when it + * is parsed with PHP. + * + * @param array(string=>mixed) $array + * @return ezcBaseFileFindContext + */ + static public function __set_state( array $array ) + { + return new ezcBaseFileFindContext( $array['elements'], $array['count'], $array['size'] ); + } +} +?> diff --git a/src/TUnit/external/ezc/Base/structs/repository_directory.php b/src/TUnit/external/ezc/Base/structs/repository_directory.php new file mode 100644 index 0000000..3c7b193 --- /dev/null +++ b/src/TUnit/external/ezc/Base/structs/repository_directory.php @@ -0,0 +1,83 @@ +type = $type; + $this->basePath = $basePath; + $this->autoloadPath = $autoloadPath; + } + + /** + * Returns a new instance of this class with the data specified by $array. + * + * $array contains all the data members of this class in the form: + * array('member_name'=>value). + * + * __set_state makes this class exportable with var_export. + * var_export() generates code, that calls this method when it + * is parsed with PHP. + * + * @param array(string=>mixed) $array + * @return ezcBaseRepositoryDirectory + */ + static public function __set_state( array $array ) + { + return new ezcBaseRepositoryDirectory( $array['type'], $array['basePath'], $array['autoloadPath'] ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/axis/container.php b/src/TUnit/external/ezc/Graph/axis/container.php new file mode 100644 index 0000000..827250c --- /dev/null +++ b/src/TUnit/external/ezc/Graph/axis/container.php @@ -0,0 +1,221 @@ +chart = $chart; + } + + /** + * Returns if the given offset exists. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key Identifier of dataset. + * @return bool True when the offset exists, otherwise false. + */ + public function offsetExists( $key ) + { + return isset( $this->data[$key] ); + } + + /** + * Returns the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key Identifier of dataset. + * @return ezcGraphChartElementAxis + * + * @throws ezcBasePropertyNotFoundException + * If no dataset with identifier exists + */ + public function offsetGet( $key ) + { + if ( !isset( $this->data[$key] ) ) + { + throw new ezcBasePropertyNotFoundException( $key ); + } + + return $this->data[$key]; + } + + /** + * Set the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key + * @param ezcGraphChartElementAxis $value + * @return void + * + * @throws ezcBaseValueException + * If supplied value is not an ezcGraphChartElementAxis + */ + public function offsetSet( $key, $value ) + { + if ( !$value instanceof ezcGraphChartElementAxis ) + { + throw new ezcBaseValueException( $key, $value, 'ezcGraphChartElementAxis' ); + } + + if ( $key === null ) + { + $key = count( $this->data ); + } + + // Add axis and configure it with current font and palette + $this->data[$key] = $value; + $value->font = $this->chart->options->font; + $value->setFromPalette( $this->chart->palette ); + + return $value; + } + + /** + * Unset the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key + * @return void + */ + public function offsetUnset( $key ) + { + if ( !isset( $this->data[$key] ) ) + { + throw new ezcBasePropertyNotFoundException( $key ); + } + + unset( $this->data[$key] ); + } + + /** + * Returns the currently selected dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return ezcGraphChartElementAxis The currently selected dataset. + */ + public function current() + { + return current( $this->data ); + } + + /** + * Returns the next dataset and selects it or false on the last dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return mixed ezcGraphChartElementAxis if the next dataset exists, or false. + */ + public function next() + { + return next( $this->data ); + } + + /** + * Returns the key of the currently selected dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return int The key of the currently selected dataset. + */ + public function key() + { + return key( $this->data ); + } + + /** + * Returns if the current dataset is valid. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return bool If the current dataset is valid + */ + public function valid() + { + return ( current( $this->data ) !== false ); + } + + /** + * Selects the very first dataset and returns it. + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return ezcGraphChartElementAxis The very first dataset. + */ + public function rewind() + { + return reset( $this->data ); + } + + /** + * Returns the number of datasets in the row. + * + * This method is part of the Countable interface to allow the usage of + * PHP's count() function to check how many datasets exist. + * + * @return int Number of datasets. + */ + public function count() + { + return count( $this->data ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/axis/date.php b/src/TUnit/external/ezc/Graph/axis/date.php new file mode 100644 index 0000000..94bb6a0 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/axis/date.php @@ -0,0 +1,633 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->options->fillLines = 210; + * $graph->title = 'Concurrent requests'; + * $graph->legend = false; + * + * $graph->xAxis = new ezcGraphChartElementDateAxis(); + * + * // Add data + * $graph->data['Machine 1'] = new ezcGraphArrayDataSet( array( + * '8:00' => 3241, + * '8:13' => 934, + * '8:24' => 1201, + * '8:27' => 1752, + * '8:51' => 123, + * ) ); + * $graph->data['Machine 2'] = new ezcGraphArrayDataSet( array( + * '8:05' => 623, + * '8:12' => 2103, + * '8:33' => 543, + * '8:43' => 2034, + * '8:59' => 3410, + * ) ); + * + * $graph->data['Machine 1']->symbol = ezcGraph::BULLET; + * $graph->data['Machine 2']->symbol = ezcGraph::BULLET; + * + * $graph->render( 400, 150, 'tutorial_axis_datetime.svg' ); + * + * + * @property float $startDate + * Starting date used to display on axis. + * @property float $endDate + * End date used to display on axis. + * @property float $interval + * Time interval between steps on axis. + * @property string $dateFormat + * Format of date string + * Like http://php.net/date + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementDateAxis extends ezcGraphChartElementAxis +{ + + const MONTH = 2629800; + + const YEAR = 31536000; + + const DECADE = 315360000; + + /** + * Minimum inserted date + * + * @var int + */ + protected $minValue = false; + + /** + * Maximum inserted date + * + * @var int + */ + protected $maxValue = false; + + /** + * Nice time intervals to used if there is no user defined interval + * + * @var array + */ + protected $predefinedIntervals = array( + // Second + 1 => 'H:i.s', + // Ten seconds + 10 => 'H:i.s', + // Thirty seconds + 30 => 'H:i.s', + // Minute + 60 => 'H:i', + // Ten minutes + 600 => 'H:i', + // Half an hour + 1800 => 'H:i', + // Hour + 3600 => 'H:i', + // Four hours + 14400 => 'H:i', + // Six hours + 21600 => 'H:i', + // Half a day + 43200 => 'd.m a', + // Day + 86400 => 'd.m', + // Week + 604800 => 'W', + // Month + self::MONTH => 'M y', + // Year + self::YEAR => 'Y', + // Decade + self::DECADE => 'Y', + ); + + /** + * Constant used for calculation of automatic definition of major scaling + * steps + */ + const MAJOR_COUNT = 10; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['startDate'] = false; + $this->properties['endDate'] = false; + $this->properties['interval'] = false; + $this->properties['dateFormat'] = false; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'startDate': + $this->properties['startDate'] = (int) $propertyValue; + break; + case 'endDate': + $this->properties['endDate'] = (int) $propertyValue; + break; + case 'interval': + $this->properties['interval'] = (int) $propertyValue; + $this->properties['initialized'] = true; + break; + case 'dateFormat': + $this->properties['dateFormat'] = (string) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Ensure proper timestamp + * + * Takes a mixed value from datasets, like timestamps, or strings + * describing some time and converts it to a timestamp. + * + * @param mixed $value + * @return int + */ + protected static function ensureTimestamp( $value ) + { + if ( is_numeric( $value ) ) + { + $timestamp = (int) $value; + } + elseif ( ( $timestamp = strtotime( $value ) ) === false ) + { + throw new ezcGraphErrorParsingDateException( $value ); + } + + return $timestamp; + } + + /** + * Add data for this axis + * + * @param array $values Value which will be displayed on this axis + * @return void + */ + public function addData( array $values ) + { + foreach ( $values as $nr => $value ) + { + $value = self::ensureTimestamp( $value ); + + if ( $this->minValue === false || + $value < $this->minValue ) + { + $this->minValue = $value; + } + + if ( $this->maxValue === false || + $value > $this->maxValue ) + { + $this->maxValue = $value; + } + } + + $this->properties['initialized'] = true; + } + + /** + * Calculate nice time interval + * + * Use the best fitting time interval defined in class property array + * predefinedIntervals. + * + * @param int $min Start time + * @param int $max End time + * @return void + */ + protected function calculateInterval( $min, $max ) + { + $diff = $max - $min; + + foreach ( $this->predefinedIntervals as $interval => $format ) + { + if ( ( $diff / $interval ) <= self::MAJOR_COUNT ) + { + break; + } + } + + if ( ( $this->properties['startDate'] !== false ) && + ( $this->properties['endDate'] !== false ) ) + { + // Use interval between defined borders + if ( ( $diff % $interval ) > 0 ) + { + // Stil use predefined date format from old interval if not set + if ( $this->properties['dateFormat'] === false ) + { + $this->properties['dateFormat'] = $this->predefinedIntervals[$interval]; + } + + $count = ceil( $diff / $interval ); + $interval = round( $diff / $count, 0 ); + } + } + + $this->properties['interval'] = $interval; + } + + /** + * Calculate lower nice date + * + * Calculates a date which is earlier or equal to the given date, and is + * divisible by the given interval. + * + * @param int $min Date + * @param int $interval Interval + * @return int Earlier date + */ + protected function calculateLowerNiceDate( $min, $interval ) + { + switch ( $interval ) + { + case self::MONTH: + // Special handling for months - not covered by the default + // algorithm + return mktime( + 1, + 0, + 0, + (int) date( 'm', $min ), + 1, + (int) date( 'Y', $min ) + ); + default: + $dateSteps = array( 60, 60, 24, 7, 52 ); + + $date = array( + (int) date( 's', $min ), + (int) date( 'i', $min ), + (int) date( 'H', $min ), + (int) date( 'd', $min ), + (int) date( 'm', $min ), + (int) date( 'Y', $min ), + ); + + $element = 0; + while ( ( $step = array_shift( $dateSteps ) ) && + ( $interval > $step ) ) + { + $interval /= $step; + $date[$element++] = (int) ( $element > 2 ); + } + + $date[$element] -= $date[$element] % $interval; + + return mktime( + $date[2], + $date[1], + $date[0], + $date[4], + $date[3], + $date[5] + ); + } + } + + /** + * Calculate start date + * + * Use calculateLowerNiceDate to get a date earlier or equal date then the + * minimum date to use it as the start date for the axis depending on the + * selected interval. + * + * @param mixed $min Minimum date + * @param mixed $max Maximum date + * @return void + */ + public function calculateMinimum( $min, $max ) + { + if ( $this->properties['endDate'] === false ) + { + $this->properties['startDate'] = $this->calculateLowerNiceDate( $min, $this->interval ); + } + else + { + $this->properties['startDate'] = $this->properties['endDate']; + + while ( $this->properties['startDate'] > $min ) + { + switch ( $this->interval ) + { + case self::MONTH: + $this->properties['startDate'] = strtotime( '-1 month', $this->properties['startDate'] ); + break; + case self::YEAR: + $this->properties['startDate'] = strtotime( '-1 year', $this->properties['startDate'] ); + break; + case self::DECADE: + $this->properties['startDate'] = strtotime( '-10 years', $this->properties['startDate'] ); + break; + default: + $this->properties['startDate'] -= $this->interval; + } + } + } + } + + /** + * Calculate end date + * + * Use calculateLowerNiceDate to get a date later or equal date then the + * maximum date to use it as the end date for the axis depending on the + * selected interval. + * + * @param mixed $min Minimum date + * @param mixed $max Maximum date + * @return void + */ + public function calculateMaximum( $min, $max ) + { + $this->properties['endDate'] = $this->properties['startDate']; + + while ( $this->properties['endDate'] < $max ) + { + switch ( $this->interval ) + { + case self::MONTH: + $this->properties['endDate'] = strtotime( '+1 month', $this->properties['endDate'] ); + break; + case self::YEAR: + $this->properties['endDate'] = strtotime( '+1 year', $this->properties['endDate'] ); + break; + case self::DECADE: + $this->properties['endDate'] = strtotime( '+10 years', $this->properties['endDate'] ); + break; + default: + $this->properties['endDate'] += $this->interval; + } + } + } + + /** + * Calculate axis bounding values on base of the assigned values + * + * @return void + */ + public function calculateAxisBoundings() + { + // Prevent division by zero, when min == max + if ( $this->minValue == $this->maxValue ) + { + if ( $this->minValue == 0 ) + { + $this->maxValue = 1; + } + else + { + $this->minValue -= ( $this->minValue * .1 ); + $this->maxValue += ( $this->maxValue * .1 ); + } + } + + // Use custom minimum and maximum if available + if ( $this->properties['startDate'] !== false ) + { + $this->minValue = $this->properties['startDate']; + } + + if ( $this->properties['endDate'] !== false ) + { + $this->maxValue = $this->properties['endDate']; + } + + // Calculate "nice" values for scaling parameters + if ( $this->properties['interval'] === false ) + { + $this->calculateInterval( $this->minValue, $this->maxValue ); + } + + if ( $this->properties['dateFormat'] === false && isset( $this->predefinedIntervals[$this->interval] ) ) + { + $this->properties['dateFormat'] = $this->predefinedIntervals[$this->interval]; + } + + if ( $this->properties['startDate'] === false ) + { + $this->calculateMinimum( $this->minValue, $this->maxValue ); + } + + if ( $this->properties['endDate'] === false ) + { + $this->calculateMaximum( $this->minValue, $this->maxValue ); + } + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param float $value Value to determine position for + * @return float Position on chart + */ + public function getCoordinate( $value ) + { + // Force typecast, because ( false < -100 ) results in (bool) true + $intValue = ( $value === false ? false : self::ensureTimestamp( $value ) ); + + if ( ( $value === false ) && + ( ( $intValue < $this->startDate ) || ( $intValue > $this->endDate ) ) ) + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return 0.; + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1.; + } + } + else + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate ); + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1 - ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate ); + } + } + } + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + public function getMinorStepCount() + { + return false; + } + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + public function getMajorStepCount() + { + return (int) ceil( ( $this->properties['endDate'] - $this->startDate ) / $this->interval ); + } + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + public function getLabel( $step ) + { + return $this->getLabelFromTimestamp( $this->startDate + ( $step * $this->interval ), $step ); + } + + /** + * Get label for timestamp + * + * @param int $time + * @param int $step + * @return string + */ + protected function getLabelFromTimestamp( $time, $step ) + { + if ( $this->properties['labelCallback'] !== null ) + { + return call_user_func_array( + $this->properties['labelCallback'], + array( + date( $this->properties['dateFormat'], $time ), + $step, + ) + ); + } + else + { + return date( $this->properties['dateFormat'], $time ); + } + } + + /** + * Return array of steps on this axis + * + * @return array( ezcGraphAxisStep ) + */ + public function getSteps() + { + $steps = array(); + + $start = $this->properties['startDate']; + $end = $this->properties['endDate']; + $distance = $end - $start; + + $step = 0; + for ( $time = $start; $time <= $end; ) + { + $steps[] = new ezcGraphAxisStep( + ( $time - $start ) / $distance, + $this->interval / $distance, + $this->getLabelFromTimestamp( $time, $step++ ), + array(), + $step === 1, + $time >= $end + ); + + switch ( $this->interval ) + { + case self::MONTH: + $time = strtotime( '+1 month', $time ); + break; + case self::YEAR: + $time = strtotime( '+1 year', $time ); + break; + case self::DECADE: + $time = strtotime( '+10 years', $time ); + break; + default: + $time += $this->interval; + break; + } + } + + return $steps; + } + + /** + * Is zero step + * + * Returns true if the given step is the one on the initial axis position + * + * @param int $step Number of step + * @return bool Status If given step is initial axis position + */ + public function isZeroStep( $step ) + { + return ( $step == 0 ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/axis/labeled.php b/src/TUnit/external/ezc/Graph/axis/labeled.php new file mode 100644 index 0000000..23f1c64 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/axis/labeled.php @@ -0,0 +1,520 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->options->fillLines = 210; + * $graph->options->font->maxFontSize = 10; + * $graph->title = 'Error level colors'; + * $graph->legend = false; + * + * $graph->yAxis = new ezcGraphChartElementLabeledAxis(); + * $graph->yAxis->axisLabelRenderer->showZeroValue = true; + * + * $graph->yAxis->label = 'Color'; + * $graph->xAxis->label = 'Error level'; + * + * // Add data + * $graph->data['colors'] = new ezcGraphArrayDataSet( + * array( + * 'info' => 'blue', + * 'notice' => 'green', + * 'warning' => 'orange', + * 'error' => 'red', + * 'fatal' => 'red', + * ) + * ); + * + * $graph->render( 400, 150, 'tutorial_axis_labeled.svg' ); + * + * + * @property float $labelCount + * Define count of displayed labels on the axis + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementLabeledAxis extends ezcGraphChartElementAxis +{ + /** + * Array with labeles for data + * + * @var array + */ + protected $labels = array(); + + /** + * Labels indexed by their name as key for faster lookups + * + * @var array + */ + protected $labelsIndexed = array(); + + /** + * Reduced amount of labels which will be displayed in the chart + * + * @var array + */ + protected $displayedLabels = array(); + + /** + * Maximum count of labels which can be displayed on one axis + * @todo Perhaps base this on the chart size + */ + const MAX_LABEL_COUNT = 10; + + /** + * Precalculated steps on the axis + * + * @var array(ezcGraphAxisStep) + */ + protected $steps; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['labelCount'] = null; + + $this->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer(); + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'labelCount': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' ); + } + + $this->properties['labelCount'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Increase the keys of all elements in the array up from the start key, to + * insert an additional element at the correct position. + * + * @param array $array Array + * @param int $startKey Key to increase keys from + * @return array Updated array + */ + protected function increaseKeys( array $array, $startKey ) + { + foreach ( $array as $key => $value ) + { + if ( $key === $startKey ) + { + // Recursive check, if next key should be increased, too + if ( isset ( $array[$key + 1] ) ) + { + $array = $this->increaseKeys( $array, $key + 1 ); + } + + // Increase key + $array[$key + 1] = $array[$key]; + unset( $array[$key] ); + } + } + + return $array; + } + + /** + * Provide initial set of labels + * + * This method may be used to provide an ordered set of labels, containing + * labels, which are not available in the datasets or to provide a label + * order different to the one in the given dataset. + * + * @param array $labels + * @return void + */ + public function provideLabels( array $labels ) + { + $this->addData( $labels ); + } + + /** + * Add data for this axis + * + * @param array $values Value which will be displayed on this axis + * @return void + */ + public function addData( array $values ) + { + $position = 0; + foreach ( $values as $label ) + { + $label = (string) $label; + + if ( !in_array( $label, $this->labels, true ) ) + { + if ( isset( $this->labels[$position] ) ) + { + $this->labels = $this->increaseKeys( $this->labels, $position ); + $this->labels[$position++] = $label; + } + else + { + $this->labels[$position++] = $label; + } + } + else + { + $position = array_search( $label, $this->labels, true ) + 1; + } + } + ksort( $this->labels ); + $this->labelsIndexed = array_flip( $this->labels ); + + $this->properties['initialized'] = true; + } + + /** + * Calculate axis bounding values on base of the assigned values + * + * @abstract + * @access public + * @return void + */ + public function calculateAxisBoundings() + { + $this->steps = array(); + + // Apply label format callback function + if ( $this->properties['labelCallback'] !== null ) + { + foreach ( $this->labels as $nr => $label ) + { + $this->labels[$nr] = call_user_func_array( + $this->properties['labelCallback'], + array( + $label, + $nr + ) + ); + } + } + + $labelCount = count( $this->labels ) - 1; + + if ( $labelCount === 0 ) + { + // Create single only step + $this->steps = array( + new ezcGraphAxisStep( + 0, + 1, + reset( $this->labels ), + array(), + true, + true + ), + ); + + return true; + } + + if ( $this->properties['labelCount'] === null ) + { + if ( $labelCount <= self::MAX_LABEL_COUNT ) + { + $stepSize = 1 / $labelCount; + + foreach ( $this->labels as $nr => $label ) + { + $this->steps[] = new ezcGraphAxisStep( + $stepSize * $nr, + $stepSize, + $label, + array(), + $nr === 0, + $nr === $labelCount + ); + } + + // @TODO: This line is deprecated and only build for + // deprecated getLabel() + $this->displayedLabels = $this->labels; + + return true; + } + + for ( $div = self::MAX_LABEL_COUNT; $div > 1; --$div ) + { + if ( ( $labelCount % $div ) === 0 ) + { + // @TODO: This part is deprecated and only build for + // deprecated getLabel() + $step = $labelCount / $div; + + foreach ( $this->labels as $nr => $label ) + { + if ( ( $nr % $step ) === 0 ) + { + $this->displayedLabels[] = $label; + } + } + // End of deprecated part + + break; + } + } + } + else + { + $div = false; + } + + // Build up step array + if ( $div > 2 ) + { + $step = $labelCount / $div; + $stepSize = 1 / $div; + $minorStepSize = $stepSize / $step; + + foreach ( $this->labels as $nr => $label ) + { + if ( ( $nr % $step ) === 0 ) + { + $mainstep = new ezcGraphAxisStep( + $stepSize * ( $nr / $step ), + $stepSize, + $label, + array(), + $nr === 0, + $nr === $labelCount + ); + + $this->steps[] = $mainstep; + } + else + { + $mainstep->childs[] = new ezcGraphAxisStep( + $mainstep->position + $minorStepSize * ( $nr % $step ), + $minorStepSize + ); + } + } + } + else + { + if ( $this->properties['labelCount'] === null ) + { + $floatStep = $labelCount / ( self::MAX_LABEL_COUNT - 1 ); + } + else + { + $floatStep = $labelCount / min( $labelCount, $this->properties['labelCount'] - 1 ); + } + + $position = 0; + $minorStepSize = 1 / $labelCount; + + foreach ( $this->labels as $nr => $label ) + { + if ( $nr >= $position ) + { + $position += $floatStep; + + // Add as major step + $mainstep = new ezcGraphAxisStep( + $minorStepSize * $nr, + ceil( $position - $nr ) * $minorStepSize, + $label, + array(), + $nr === 0, + $nr === $labelCount + ); + + // @TODO: This line is deprecated and only build for + // deprecated getLabel() + $this->displayedLabels[] = $label; + + $this->steps[] = $mainstep; + } + else + { + $mainstep->childs[] = new ezcGraphAxisStep( + $minorStepSize * $nr, + $minorStepSize + ); + } + } + } + } + + /** + * Return array of steps on this axis + * + * @return array( ezcGraphAxisStep ) + */ + public function getSteps() + { + return $this->steps; + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param string $value Value to determine position for + * @return float Position on chart + */ + public function getCoordinate( $value ) + { + if ( ( $value === false ) || + ( $value === null ) || + ( !isset( $this->labelsIndexed[$value] ) ) ) + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return 0.; + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1.; + } + } + else + { + $key = $this->labelsIndexed[$value]; + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + if ( count( $this->labels ) > 1 ) + { + return (float) $key / ( count ( $this->labels ) - 1 ); + } + else + { + return 0; + } + case ezcGraph::BOTTOM: + case ezcGraph::RIGHT: + if ( count( $this->labels ) > 1 ) + { + return (float) 1 - $key / ( count ( $this->labels ) - 1 ); + } + else + { + return 1; + } + } + } + } + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + public function getMinorStepCount() + { + return 0; + } + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + public function getMajorStepCount() + { + return max( count( $this->displayedLabels ) - 1, 1 ); + } + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + public function getLabel( $step ) + { + if ( isset( $this->displayedLabels[$step] ) ) + { + return $this->displayedLabels[$step]; + } + else + { + return false; + } + } + + /** + * Is zero step + * + * Returns true if the given step is the one on the initial axis position + * + * @param int $step Number of step + * @return bool Status If given step is initial axis position + */ + public function isZeroStep( $step ) + { + return !$step; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/axis/logarithmic.php b/src/TUnit/external/ezc/Graph/axis/logarithmic.php new file mode 100644 index 0000000..53c82ef --- /dev/null +++ b/src/TUnit/external/ezc/Graph/axis/logarithmic.php @@ -0,0 +1,341 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->title = 'The power of x'; + * $graph->legend->position = ezcGraph::BOTTOM; + * + * $graph->xAxis = new ezcGraphChartElementNumericAxis(); + * $graph->yAxis = new ezcGraphChartElementLogarithmicalAxis(); + * + * $graph->data['x^2'] = new ezcGraphNumericDataSet( + * -10, 10, + * create_function( '$x', 'return pow( $x, 2 ) + 1;' ) + * ); + * + * $graph->data['x^4'] = new ezcGraphNumericDataSet( + * -10, 10, + * create_function( '$x', 'return pow( $x, 4 ) + 1;' ) + * ); + * + * $graph->data['x^6'] = new ezcGraphNumericDataSet( + * -10, 10, + * create_function( '$x', 'return pow( $x, 6 ) + 1;' ) + * ); + * + * $graph->render( 400, 250, 'tutorial_axis_logarithmic.svg' ); + * + * + * @property float $base + * Base for logarithmical scaling. + * @property string $logarithmicalFormatString + * Sprintf formatstring for the axis labels where + * $1 is the base and + * $2 is the exponent. + * @property-read float $minValue + * Minimum Value to display on this axis. + * @property-read float $maxValue + * Maximum value to display on this axis. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementLogarithmicalAxis extends ezcGraphChartElementAxis +{ + + /** + * Constant used for calculation of automatic definition of major scaling + * steps + */ + const MAX_STEPS = 9; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['min'] = null; + $this->properties['max'] = null; + $this->properties['base'] = 10; + $this->properties['logarithmicalFormatString'] = '%1$d^%2$d'; + $this->properties['minValue'] = null; + $this->properties['maxValue'] = null; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'min': + case 'max': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + $this->properties['initialized'] = true; + break; + case 'base': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + case 'logarithmicalFormatString': + $this->properties['logarithmicalFormatString'] = (string) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Add data for this axis + * + * @param array $values Value which will be displayed on this axis + * @return void + */ + public function addData( array $values ) + { + foreach ( $values as $value ) + { + if ( $this->properties['minValue'] === null || + $value < $this->properties['minValue'] ) + { + $this->properties['minValue'] = $value; + } + + if ( $this->properties['maxValue'] === null || + $value > $this->properties['maxValue'] ) + { + $this->properties['maxValue'] = $value; + } + } + + $this->properties['initialized'] = true; + } + + /** + * Calculate axis bounding values on base of the assigned values + * + * @abstract + * @access public + * @return void + */ + public function calculateAxisBoundings() + { + // Prevent division by zero, when min == max + if ( $this->properties['minValue'] == $this->properties['maxValue'] ) + { + if ( $this->properties['minValue'] == 0 ) + { + $this->properties['maxValue'] = 1; + } + else + { + $this->properties['minValue'] -= ( $this->properties['minValue'] * .1 ); + $this->properties['maxValue'] += ( $this->properties['maxValue'] * .1 ); + } + } + + if ( $this->properties['minValue'] <= 0 ) + { + throw new ezcGraphOutOfLogithmicalBoundingsException( $this->properties['minValue'] ); + } + + // Use custom minimum and maximum if available + if ( $this->properties['min'] !== null ) + { + $this->properties['minValue'] = pow( $this->properties['base'], $this->properties['min'] ); + } + + if ( $this->properties['max'] !== null ) + { + $this->properties['maxValue'] = pow( $this->properties['base'], $this->properties['max'] ); + } + + // Calculate "nice" values for scaling parameters + if ( $this->properties['min'] === null ) + { + $this->properties['min'] = floor( log( $this->properties['minValue'], $this->properties['base'] ) ); + } + + if ( $this->properties['max'] === null ) + { + $this->properties['max'] = ceil( log( $this->properties['maxValue'], $this->properties['base'] ) ); + } + + $this->properties['minorStep'] = 1; + if ( ( $modifier = ( ( $this->properties['max'] - $this->properties['min'] ) / self::MAX_STEPS ) ) > 1 ) + { + $this->properties['majorStep'] = $modifier = ceil( $modifier ); + $this->properties['min'] = floor( $this->properties['min'] / $modifier ) * $modifier; + $this->properties['max'] = floor( $this->properties['max'] / $modifier ) * $modifier; + } + else + { + $this->properties['majorStep'] = 1; + } + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param float $value Value to determine position for + * @return float Position on chart + */ + public function getCoordinate( $value ) + { + // Force typecast, because ( false < -100 ) results in (bool) true + $floatValue = (float) $value; + + if ( $value === false ) + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return 0.; + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1.; + } + } + else + { + $position = ( log( $value, $this->properties['base'] ) - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] ); + + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return $position; + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1 - $position; + } + } + } + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + public function getMinorStepCount() + { + return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['minorStep'] ); + } + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + public function getMajorStepCount() + { + return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['majorStep'] ); + } + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + public function getLabel( $step ) + { + if ( $this->properties['labelCallback'] !== null ) + { + return call_user_func_array( + $this->properties['labelCallback'], + array( + sprintf( + $this->properties['logarithmicalFormatString'], + $this->properties['base'], + $this->properties['min'] + ( $step * $this->properties['majorStep'] ) + ), + $step, + ) + ); + } + else + { + return sprintf( + $this->properties['logarithmicalFormatString'], + $this->properties['base'], + $this->properties['min'] + ( $step * $this->properties['majorStep'] ) + ); + } + } + + /** + * Is zero step + * + * Returns true if the given step is the one on the initial axis position + * + * @param int $step Number of step + * @return bool Status If given step is initial axis position + */ + public function isZeroStep( $step ) + { + return ( $step == 0 ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/axis/numeric.php b/src/TUnit/external/ezc/Graph/axis/numeric.php new file mode 100644 index 0000000..ce38b45 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/axis/numeric.php @@ -0,0 +1,501 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->title = 'Some random data'; + * $graph->legend = false; + * + * $graph->xAxis = new ezcGraphChartElementNumericAxis(); + * // The y axis is numeric by default. + * + * $graph->xAxis->min = -15; + * $graph->xAxis->max = 15; + * $graph->xAxis->majorStep = 5; + * + * $data = array( + * array(), + * array() + * ); + * for ( $i = -10; $i <= 10; $i++ ) + * { + * $data[0][$i] = mt_rand( -23, 59 ); + * $data[1][$i] = mt_rand( -23, 59 ); + * } + * + * // Add data + * $graph->data['random blue'] = new ezcGraphArrayDataSet( $data[0] ); + * $graph->data['random green'] = new ezcGraphArrayDataSet( $data[1] ); + * + * $graph->render( 400, 150, 'tutorial_axis_numeric.svg' ); + * + * + * @property float $min + * Minimum value of displayed scale on axis. + * @property float $max + * Maximum value of displayed scale on axis. + * @property mixed $majorStep + * Labeled major steps displayed on the axis. + * @property mixed $minorStep + * Non labeled minor steps on the axis. + * @property-read float $minValue + * Minimum Value to display on this axis. + * @property-read float $maxValue + * Maximum value to display on this axis. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementNumericAxis extends ezcGraphChartElementAxis +{ + + /** + * Constant used for calculation of automatic definition of major scaling + * steps + */ + const MIN_MAJOR_COUNT = 5; + + /** + * Constant used for automatic calculation of minor steps from given major + * steps + */ + const MIN_MINOR_COUNT = 8; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['min'] = null; + $this->properties['max'] = null; + $this->properties['minValue'] = null; + $this->properties['maxValue'] = null; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'min': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' ); + } + + $this->properties['min'] = (float) $propertyValue; + $this->properties['initialized'] = true; + break; + case 'max': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' ); + } + + $this->properties['max'] = (float) $propertyValue; + $this->properties['initialized'] = true; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Returns a "nice" number for a given floating point number. + * + * Nice numbers are steps on a scale which are easily recognized by humans + * like 0.5, 25, 1000 etc. + * + * @param float $float Number to be altered + * @return float Nice number + */ + protected function getNiceNumber( $float ) + { + // Get absolute value and save sign + $abs = abs( $float ); + $sign = $float / $abs; + + // Normalize number to a range between 1 and 10 + $log = (int) round( log10( $abs ), 0 ); + $abs /= pow( 10, $log ); + + + // find next nice number + if ( $abs > 5 ) + { + $abs = 10.; + } + elseif ( $abs > 2.5 ) + { + $abs = 5.; + } + elseif ( $abs > 1 ) + { + $abs = 2.5; + } + else + { + $abs = 1; + } + + // unnormalize number to original values + return $abs * pow( 10, $log ) * $sign; + } + + /** + * Calculate minimum value for displayed axe basing on real minimum and + * major step size + * + * @param float $min Real data minimum + * @param float $max Real data maximum + * @return void + */ + protected function calculateMinimum( $min, $max ) + { + if ( $this->properties['max'] === null ) + { + $this->properties['min'] = floor( $min / $this->properties['majorStep'] ) * $this->properties['majorStep']; + } + else + { + $calculatedMin = $this->properties['max']; + + do { + $calculatedMin -= $this->properties['majorStep']; + } while ( $calculatedMin > $min ); + + $this->properties['min'] = $calculatedMin; + } + } + + /** + * Calculate maximum value for displayed axe basing on real maximum and + * major step size + * + * @param float $min Real data minimum + * @param float $max Real data maximum + * @return void + */ + protected function calculateMaximum( $min, $max ) + { + $calculatedMax = $this->properties['min']; + + do { + $calculatedMax += $this->properties['majorStep']; + } while ( $calculatedMax < $max ); + + $this->properties['max'] = $calculatedMax; + } + + /** + * Calculate size of minor steps based on the size of the major step size + * + * @param float $min Real data minimum + * @param float $max Real data maximum + * @return void + */ + protected function calculateMinorStep( $min, $max ) + { + $stepSize = $this->properties['majorStep'] / self::MIN_MINOR_COUNT; + $this->properties['minorStep'] = $this->getNiceNumber( $stepSize ); + } + + /** + * Calculate size of major step based on the span to be displayed and the + * defined MIN_MAJOR_COUNT constant. + * + * @param float $min Real data minimum + * @param float $max Real data maximum + * @return void + */ + protected function calculateMajorStep( $min, $max ) + { + $span = $max - $min; + $stepSize = $span / self::MIN_MAJOR_COUNT; + $this->properties['majorStep'] = $this->getNiceNumber( $stepSize ); + } + + /** + * Add data for this axis + * + * @param array $values Value which will be displayed on this axis + * @return void + */ + public function addData( array $values ) + { + foreach ( $values as $value ) + { + if ( $this->properties['minValue'] === null || + $value < $this->properties['minValue'] ) + { + $this->properties['minValue'] = $value; + } + + if ( $this->properties['maxValue'] === null || + $value > $this->properties['maxValue'] ) + { + $this->properties['maxValue'] = $value; + } + } + + $this->properties['initialized'] = true; + } + + /** + * Calculate axis bounding values on base of the assigned values + * + * @abstract + * @access public + * @return void + */ + public function calculateAxisBoundings() + { + // Prevent division by zero, when min == max + if ( $this->properties['minValue'] == $this->properties['maxValue'] ) + { + if ( $this->properties['minValue'] == 0 ) + { + $this->properties['maxValue'] = 1; + } + else + { + if ( $this->properties['majorStep'] !== null ) + { + $this->properties['minValue'] -= $this->properties['majorStep']; + $this->properties['maxValue'] += $this->properties['majorStep']; + } + else + { + $this->properties['minValue'] -= ( $this->properties['minValue'] * .1 ); + $this->properties['maxValue'] += ( $this->properties['maxValue'] * .1 ); + } + } + } + + // Use custom minimum and maximum if available + if ( $this->properties['min'] !== null ) + { + $this->properties['minValue'] = $this->properties['min']; + } + + if ( $this->properties['max'] !== null ) + { + $this->properties['maxValue'] = $this->properties['max']; + } + + // If min and max values are forced, we may not be able to find a + // "nice" number for the steps. Try to find such a nice step size, or + // fall back to a step size, which is just the span divided by 5. + if ( ( $this->properties['min'] !== null ) && + ( $this->properties['max'] !== null ) && + ( $this->properties['majorStep'] === null ) ) + { + $diff = $this->properties['max'] - $this->properties['min']; + $this->calculateMajorStep( $this->properties['minValue'], $this->properties['maxValue'] ); + $stepInvariance = $diff / $this->properties['majorStep']; + if ( ( $stepInvariance - floor( $stepInvariance ) ) > .0000001 ) + { + // For too big step invariances calculate the step size just + // from the given difference between min and max value. + $this->properties['majorStep'] = ( $this->properties['max'] - $this->properties['min'] ) / self::MIN_MAJOR_COUNT; + $this->properties['minorStep'] = $this->properties['majorStep'] / self::MIN_MAJOR_COUNT; + } + } + + // Calculate "nice" values for scaling parameters + if ( $this->properties['majorStep'] === null ) + { + $this->calculateMajorStep( $this->properties['minValue'], $this->properties['maxValue'] ); + } + + if ( $this->properties['minorStep'] === null ) + { + $this->calculateMinorStep( $this->properties['minValue'], $this->properties['maxValue'] ); + } + + if ( $this->properties['min'] === null ) + { + $this->calculateMinimum( $this->properties['minValue'], $this->properties['maxValue'] ); + } + + if ( $this->properties['max'] === null ) + { + $this->calculateMaximum( $this->properties['minValue'], $this->properties['maxValue'] ); + } + + // Check that the major step size matches up with the min and max + // values on the axis. + $quotient = ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['majorStep']; + $quotient = abs( $quotient - floor( $quotient ) ); + if ( ( $quotient >= .00001 ) && + ( abs( $quotient - 1 ) >= .00001 ) ) + { + throw new ezcGraphInvalidStepSizeException( "The difference between minimum and maximum value is not a multiplier of the major step size." ); + } + + // Check that the minor step size matches up with major step size on + // the axis. + $quotient = $this->properties['majorStep'] / $this->properties['minorStep']; + $quotient = abs( $quotient - floor( $quotient ) ); + if ( ( $quotient >= .00001 ) && + ( abs( $quotient - 1 ) >= .00001 ) ) + { + throw new ezcGraphInvalidStepSizeException( "The major step size value is not a multiplier of the minor step size." ); + } + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param float $value Value to determine position for + * @return float Position on chart + */ + public function getCoordinate( $value ) + { + // Force typecast, because ( false < -100 ) results in (bool) true + $floatValue = (float) $value; + + if ( ( $value === false ) && + ( ( $floatValue < $this->properties['min'] ) || ( $floatValue > $this->properties['max'] ) ) ) + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return 0.; + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1.; + } + } + else + { + switch ( $this->position ) + { + case ezcGraph::LEFT: + case ezcGraph::TOP: + return ( $value - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] ); + case ezcGraph::RIGHT: + case ezcGraph::BOTTOM: + return 1 - ( $value - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] ); + } + } + } + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + public function getMinorStepCount() + { + return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['minorStep'] ); + } + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + public function getMajorStepCount() + { + return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['majorStep'] ); + } + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + public function getLabel( $step ) + { + if ( $this->properties['labelCallback'] !== null ) + { + return call_user_func_array( + $this->properties['labelCallback'], + array( + $this->properties['min'] + ( $step * $this->properties['majorStep'] ), + $step, + ) + ); + } + else + { + return $this->properties['min'] + ( $step * $this->properties['majorStep'] ); + } + } + + /** + * Is zero step + * + * Returns true if the given step is the one on the initial axis position + * + * @param int $step Number of step + * @return bool Status If given step is initial axis position + */ + public function isZeroStep( $step ) + { + return ( $this->getLabel( $step ) == 0 ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/charts/bar.php b/src/TUnit/external/ezc/Graph/charts/bar.php new file mode 100644 index 0000000..2806b21 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/charts/bar.php @@ -0,0 +1,94 @@ + + * // Create a new line chart + * $chart = new ezcGraphBarChart(); + * + * // Add data to line chart + * $chart->data['sample dataset'] = new ezcGraphArrayDataSet( + * array( + * '100' => 1.2, + * '200' => 43.2, + * '300' => -34.14, + * '350' => 65, + * '400' => 123, + * ) + * ); + * + * // Render chart with default 2d renderer and default SVG driver + * $chart->render( 500, 200, 'bar_chart.svg' ); + * + * + * Each chart consists of several chart elements which represents logical + * parts of the chart and can be formatted independently. The bar chart + * consists of: + * - title ( {@link ezcGraphChartElementText} ) + * - legend ( {@link ezcGraphChartElementLegend} ) + * - background ( {@link ezcGraphChartElementBackground} ) + * - xAxis ( {@link ezcGraphChartElementLabeledAxis} ) + * - yAxis ( {@link ezcGraphChartElementNumericAxis} ) + * + * The type of the axis may be changed and all elements can be configured by + * accessing them as properties of the chart: + * + * + * $chart->legend->position = ezcGraph::RIGHT; + * + * + * The chart itself also offers several options to configure the appearance. As + * bar charts extend line charts the the extended configure options are + * available in {@link ezcGraphLineChartOptions} extending the + * {@link ezcGraphChartOptions}. + * + * @property ezcGraphLineChartOptions $options + * Chart options class + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphBarChart extends ezcGraphLineChart +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + parent::__construct(); + + $this->elements['xAxis']->axisLabelRenderer = new ezcGraphAxisBoxedLabelRenderer(); + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + public function getDefaultDisplayType() + { + return ezcGraph::BAR; + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/charts/line.php b/src/TUnit/external/ezc/Graph/charts/line.php new file mode 100644 index 0000000..e95b36e --- /dev/null +++ b/src/TUnit/external/ezc/Graph/charts/line.php @@ -0,0 +1,715 @@ + + * // Create a new line chart + * $chart = new ezcGraphLineChart(); + * + * // Add data to line chart + * $chart->data['sample dataset'] = new ezcGraphArrayDataSet( + * array( + * '100' => 1.2, + * '200' => 43.2, + * '300' => -34.14, + * '350' => 65, + * '400' => 123, + * ) + * ); + * + * // Render chart with default 2d renderer and default SVG driver + * $chart->render( 500, 200, 'line_chart.svg' ); + * + * + * Each chart consists of several chart elements which represents logical + * parts of the chart and can be formatted independently. The line chart + * consists of: + * - title ( {@link ezcGraphChartElementText} ) + * - legend ( {@link ezcGraphChartElementLegend} ) + * - background ( {@link ezcGraphChartElementBackground} ) + * - xAxis ( {@link ezcGraphChartElementLabeledAxis} ) + * - yAxis ( {@link ezcGraphChartElementNumericAxis} ) + * + * The type of the axis may be changed and all elements can be configured by + * accessing them as properties of the chart: + * + * + * $chart->legend->position = ezcGraph::RIGHT; + * + * + * The chart itself also offers several options to configure the appearance. + * The extended configure options are available in + * {@link ezcGraphLineChartOptions} extending the {@link ezcGraphChartOptions}. + * + * @property ezcGraphLineChartOptions $options + * Chart options class + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphLineChart extends ezcGraphChart +{ + /** + * Array with additional axis for the chart + * + * @var ezcGraphAxisContainer + */ + protected $additionalAxis; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->additionalAxis = new ezcGraphAxisContainer( $this ); + + $this->options = new ezcGraphLineChartOptions( $options ); + $this->options->highlightFont = $this->options->font; + + parent::__construct(); + + $this->addElement( 'xAxis', new ezcGraphChartElementLabeledAxis() ); + $this->elements['xAxis']->position = ezcGraph::LEFT; + + $this->addElement( 'yAxis', new ezcGraphChartElementNumericAxis() ); + $this->elements['yAxis']->position = ezcGraph::BOTTOM; + } + + /** + * __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 'additionalAxis': + return $this->additionalAxis; + } + + return parent::__get( $propertyName ); + } + + /** + * Options write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param mixed $propertyName Option name + * @param mixed $propertyValue Option value; + * @return mixed + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'xAxis': + if ( $propertyValue instanceof ezcGraphChartElementAxis ) + { + $this->addElement( 'xAxis', $propertyValue ); + $this->elements['xAxis']->position = ezcGraph::LEFT; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); + } + break; + case 'yAxis': + if ( $propertyValue instanceof ezcGraphChartElementAxis ) + { + $this->addElement( 'yAxis', $propertyValue ); + $this->elements['yAxis']->position = ezcGraph::BOTTOM; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); + } + break; + default: + parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Set colors and border for this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + foreach ( $this->additionalAxis as $element ) + { + $element->setFromPalette( $palette ); + } + + parent::setFromPalette( $palette ); + } + + /** + * Render the assigned data + * + * Will renderer all charts data in the remaining boundings after drawing + * all other chart elements. The data will be rendered depending on the + * settings in the dataset. + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Remaining boundings + * @return void + */ + protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphBoundings $innerBoundings ) + { + // Use inner boundings for drawning chart data + $boundings = $innerBoundings; + + $yAxisNullPosition = $this->elements['yAxis']->getCoordinate( false ); + + // Initialize counters + $nr = array(); + $count = array(); + + foreach ( $this->data as $data ) + { + if ( !isset( $nr[$data->displayType->default] ) ) + { + $nr[$data->displayType->default] = 0; + $count[$data->displayType->default] = 0; + } + + $nr[$data->displayType->default]++; + $count[$data->displayType->default]++; + } + + $checkedRegularSteps = false; + + // Display data + foreach ( $this->data as $datasetName => $data ) + { + --$nr[$data->displayType->default]; + + // Check which axis should be used + $xAxis = ( $data->xAxis->default ? $data->xAxis->default: $this->elements['xAxis'] ); + $yAxis = ( $data->yAxis->default ? $data->yAxis->default: $this->elements['yAxis'] ); + + // Determine fill color for dataset + if ( $this->options->fillLines !== false ) + { + $fillColor = clone $data->color->default; + $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) ); + } + else + { + $fillColor = null; + } + + // Ensure regular steps on axis when used with bar charts and + // precalculate some values use to render bar charts + // + // Called only once and only when bars should be rendered + if ( ( $checkedRegularSteps === false ) && + ( $data->displayType->default === ezcGraph::BAR ) ) + { + $steps = $xAxis->getSteps(); + + $stepWidth = null; + foreach ( $steps as $step ) + { + if ( $stepWidth === null ) + { + $stepWidth = $step->width; + } + elseif ( $step->width !== $stepWidth ) + { + throw new ezcGraphUnregularStepsException(); + } + } + + $step = reset( $steps ); + if ( count( $step->childs ) ) + { + // Keep this for BC reasons + $barCount = ( $xAxis->getMajorStepCount() + 1 ) * ( $xAxis->getMinorStepCount() - 1 ); + $stepWidth = 1 / $barCount; + } + + $checkedRegularSteps = true; + $width = $xAxis->axisLabelRenderer->modifyChartDataPosition( + $yAxis->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $stepWidth, + 0 + ) + ) + )->x; + } + + // Draw lines for dataset + $lastPoint = false; + foreach ( $data as $key => $value ) + { + // Calculate point in chart + $point = $xAxis->axisLabelRenderer->modifyChartDataPosition( + $yAxis->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + $xAxis->getCoordinate( $key ), + $yAxis->getCoordinate( $value ) + ) + ) + ); + + // Render depending on display type of dataset + switch ( true ) + { + case $data->displayType->default === ezcGraph::LINE: + $renderer->drawDataLine( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $data->color->default, + ( $lastPoint === false ? $point : $lastPoint ), + $point, + $nr[$data->displayType->default], + $count[$data->displayType->default], + $data->symbol[$key], + $data->color[$key], + $fillColor, + $yAxisNullPosition, + ( $data->lineThickness->default ? $data->lineThickness->default : $this->options->lineThickness ) + ); + + // Render highlight string if requested + if ( $data->highlight[$key] ) + { + $renderer->drawDataHighlightText( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $point, + $yAxisNullPosition, + $nr[$data->displayType->default], + $count[$data->displayType->default], + $this->options->highlightFont, + ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), + $this->options->highlightSize + $this->options->highlightFont->padding * 2, + ( $this->options->highlightLines ? $data->color[$key] : null ), + ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), + ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), + 0., + ezcGraph::LINE + ); + } + break; + case ( $data->displayType->default === ezcGraph::BAR ) && + $this->options->stackBars : + // Check if a bar has already been stacked + if ( !isset( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) ) + { + $start = new ezcGraphCoordinate( + $point->x, + $yAxisNullPosition + ); + + $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] = $value; + } + else + { + $start = $xAxis->axisLabelRenderer->modifyChartDataPosition( + $yAxis->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + $xAxis->getCoordinate( $key ), + $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) + ) + ) + ); + + $point = $xAxis->axisLabelRenderer->modifyChartDataPosition( + $yAxis->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + $xAxis->getCoordinate( $key ), + $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] += $value ) + ) + ) + ); + } + + // Force one symbol for each stacked bar + if ( !isset( $stackedSymbol[(int) ( $point->x * 10000 )] ) ) + { + $stackedSymbol[(int) ( $point->x * 10000 )] = $data->symbol[$key]; + } + + // Store stacked value for next iteration + $side = ( $point->y == 0 ? 1 : $point->y / abs( $point->y ) ); + $stacked[(int) ( $point->x * 10000 )][$side] = $point; + + $renderer->drawStackedBar( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $data->color->default, + $start, + $point, + $width, + $stackedSymbol[(int) ( $point->x * 10000 )], + $yAxisNullPosition + ); + + // Render highlight string if requested + if ( $data->highlight[$key] ) + { + $renderer->drawDataHighlightText( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $point, + $yAxisNullPosition, + $nr[$data->displayType->default], + $count[$data->displayType->default], + $this->options->highlightFont, + ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), + $this->options->highlightSize + $this->options->highlightFont->padding * 2, + ( $this->options->highlightLines ? $data->color[$key] : null ), + ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), + ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), + 0., + ezcGraph::LINE + ); + } + break; + case $data->displayType->default === ezcGraph::BAR: + $renderer->drawBar( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $data->color[$key], + $point, + $width, + $nr[$data->displayType->default], + $count[$data->displayType->default], + $data->symbol[$key], + $yAxisNullPosition + ); + + // Render highlight string if requested + if ( $data->highlight[$key] ) + { + $renderer->drawDataHighlightText( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $point, + $yAxisNullPosition, + $nr[$data->displayType->default], + $count[$data->displayType->default], + $this->options->highlightFont, + ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ), + $this->options->highlightSize + $this->options->highlightFont->padding * 2, + ( $this->options->highlightLines ? $data->color[$key] : null ), + ( $this->options->highlightXOffset ? $this->options->highlightXOffset : 0 ), + ( $this->options->highlightYOffset ? $this->options->highlightYOffset : 0 ), + $width, + $data->displayType->default + ); + } + break; + default: + throw new ezcGraphInvalidDisplayTypeException( $data->displayType->default ); + break; + } + + // Store last point, used to connect lines in line chart. + $lastPoint = $point; + } + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + public function getDefaultDisplayType() + { + return ezcGraph::LINE; + } + + /** + * Check if renderer supports features requested by some special chart + * options. + * + * @throws ezcBaseValueException + * If some feature is not supported + * + * @return void + */ + protected function checkRenderer() + { + // When stacked bars are enabled, check if renderer supports them + if ( $this->options->stackBars ) + { + if ( !$this->renderer instanceof ezcGraphStackedBarsRenderer ) + { + throw new ezcBaseValueException( 'renderer', $this->renderer, 'ezcGraphStackedBarsRenderer' ); + } + } + } + + /** + * Aggregate and calculate value boundings on axis. + * + * @return void + */ + protected function setAxisValues() + { + // Virtual data set build for agrregated values sums for bar charts + $virtualBarSumDataSet = array( array(), array() ); + + // Calculate axis scaling and labeling + foreach ( $this->data as $dataset ) + { + $nr = 0; + $labels = array(); + $values = array(); + foreach ( $dataset as $label => $value ) + { + $labels[] = $label; + $values[] = $value; + + // Build sum of all bars + if ( $this->options->stackBars && + ( $dataset->displayType->default === ezcGraph::BAR ) ) + { + if ( !isset( $virtualBarSumDataSet[(int) $value >= 0][$nr] ) ) + { + $virtualBarSumDataSet[(int) $value >= 0][$nr++] = $value; + } + else + { + $virtualBarSumDataSet[(int) $value >= 0][$nr++] += $value; + } + } + } + + // Check if data has been associated with another custom axis, use + // default axis otherwise. + if ( $dataset->xAxis->default ) + { + $dataset->xAxis->default->addData( $labels ); + } + else + { + $this->elements['xAxis']->addData( $labels ); + } + + if ( $dataset->yAxis->default ) + { + $dataset->yAxis->default->addData( $values ); + } + else + { + $this->elements['yAxis']->addData( $values ); + } + } + + // Also use stacked bar values as base for y axis value span + // calculation + if ( $this->options->stackBars ) + { + $this->elements['yAxis']->addData( $virtualBarSumDataSet[0] ); + $this->elements['yAxis']->addData( $virtualBarSumDataSet[1] ); + } + + // There should always be something assigned to the main x and y axis. + if ( !$this->elements['xAxis']->initialized || + !$this->elements['yAxis']->initialized ) + { + throw new ezcGraphNoDataException(); + } + + // Calculate boundings from assigned data + $this->elements['xAxis']->calculateAxisBoundings(); + $this->elements['yAxis']->calculateAxisBoundings(); + } + + /** + * Renders the basic elements of this chart type + * + * @param int $width + * @param int $height + * @return void + */ + protected function renderElements( $width, $height ) + { + if ( !count( $this->data ) ) + { + throw new ezcGraphNoDataException(); + } + + // Check if renderer supports requested features + $this->checkRenderer(); + + // Set values form datasets on axis to calculate correct spans + $this->setAxisValues(); + + // Generate legend + $this->elements['legend']->generateFromDataSets( $this->data ); + + // Get boundings from parameters + $this->options->width = $width; + $this->options->height = $height; + + // Set image properties in driver + $this->driver->options->width = $width; + $this->driver->options->height = $height; + + // Render subelements + $boundings = new ezcGraphBoundings(); + $boundings->x1 = $this->options->width; + $boundings->y1 = $this->options->height; + + $boundings = $this->elements['xAxis']->axisLabelRenderer->modifyChartBoundings( + $this->elements['yAxis']->axisLabelRenderer->modifyChartBoundings( + $boundings, new ezcGraphCoordinate( 1, 0 ) + ), new ezcGraphCoordinate( -1, 0 ) + ); + + // Render subelements + foreach ( $this->elements as $name => $element ) + { + // Skip element, if it should not get rendered + if ( ( $this->renderElement[$name] === false ) || + ( $name === 'xAxis' ) || + ( $name === 'yAxis' ) ) + { + continue; + } + + $this->driver->options->font = $element->font; + $boundings = $element->render( $this->renderer, $boundings ); + } + + // Set relative positions of axis in chart depending on the "null" + // value on the other axis. + $this->elements['xAxis']->nullPosition = $this->elements['yAxis']->getCoordinate( false ); + $this->elements['yAxis']->nullPosition = $this->elements['xAxis']->getCoordinate( false ); + + // Calculate inner data boundings of chart + $innerBoundings = new ezcGraphBoundings( + $boundings->x0 + $boundings->width * + ( ( ( $this->elements['yAxis']->outerAxisSpace === null ) || + ( $this->elements['xAxis']->position === ezcGraph::LEFT ) ) ? + $this->elements['yAxis']->axisSpace : + $this->elements['yAxis']->outerAxisSpace ), + $boundings->y0 + $boundings->height * + ( ( ( $this->elements['xAxis']->outerAxisSpace === null ) || + ( $this->elements['yAxis']->position === ezcGraph::TOP ) ) ? + $this->elements['xAxis']->axisSpace : + $this->elements['yAxis']->outerAxisSpace ), + $boundings->x1 - $boundings->width * + ( ( ( $this->elements['yAxis']->outerAxisSpace === null ) || + ( $this->elements['xAxis']->position === ezcGraph::RIGHT ) ) ? + $this->elements['yAxis']->axisSpace : + $this->elements['yAxis']->outerAxisSpace ), + $boundings->y1 - $boundings->height * + ( ( ( $this->elements['xAxis']->outerAxisSpace === null ) || + ( $this->elements['yAxis']->position === ezcGraph::BOTTOM ) ) ? + $this->elements['xAxis']->axisSpace : + $this->elements['yAxis']->outerAxisSpace ) + ); + + // Render axis + $this->driver->options->font = $this->elements['yAxis']->font; + $boundings = $this->elements['xAxis']->render( $this->renderer, $boundings, $innerBoundings ); + $boundings = $this->elements['yAxis']->render( $this->renderer, $boundings, $innerBoundings ); + + // Render additional axis + foreach ( $this->additionalAxis as $element ) + { + if ( $element->initialized ) + { + // Calculate all required step sizes if values has been + // assigned to axis. + $element->calculateAxisBoundings(); + } + else + { + // Do not render any axis labels, if no values were assigned + // and no step sizes were defined. + $element->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer(); + } + + $this->driver->options->font = $element->font; + $element->nullPosition = $element->chartPosition; + $boundings = $element->render( $this->renderer, $boundings, $innerBoundings ); + } + + // Render graph + $this->renderData( $this->renderer, $boundings, $innerBoundings ); + } + + /** + * Render the line chart + * + * Renders the chart into a file or stream. The width and height are + * needed to specify the dimensions of the resulting image. For direct + * output use 'php://stdout' as output file. + * + * @param int $width Image width + * @param int $height Image height + * @param string $file Output file + * @apichange + * @return void + */ + public function render( $width, $height, $file = null ) + { + $this->renderElements( $width, $height ); + + if ( !empty( $file ) ) + { + $this->renderer->render( $file ); + } + + $this->renderedFile = $file; + } + + /** + * Renders this chart to direct output + * + * Does the same as ezcGraphChart::render(), but renders directly to + * output and not into a file. + * + * @param int $width + * @param int $height + * @apichange + * @return void + */ + public function renderToOutput( $width, $height ) + { + // @TODO: merge this function with render an deprecate ommit of third + // argument in render() when API break is possible + $this->renderElements( $width, $height ); + $this->renderer->render( null ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/charts/odometer.php b/src/TUnit/external/ezc/Graph/charts/odometer.php new file mode 100644 index 0000000..4db240b --- /dev/null +++ b/src/TUnit/external/ezc/Graph/charts/odometer.php @@ -0,0 +1,296 @@ + + * $graph = new ezcGraphOdometerChart(); + * $graph->title = 'Custom odometer'; + * + * $graph->data['data'] = new ezcGraphArrayDataSet( + * array( 87 ) + * ); + * + * // Set the marker color + * $graph->data['data']->color[0] = '#A0000055'; + * + * // Set colors for the background gradient + * $graph->options->startColor = '#2E3436'; + * $graph->options->endColor = '#EEEEEC'; + * + * // Define a border for the odometer + * $graph->options->borderWidth = 2; + * $graph->options->borderColor = '#BABDB6'; + * + * // Set marker width + * $graph->options->markerWidth = 5; + * + * // Set space, which the odometer may consume + * $graph->options->odometerHeight = .7; + * + * // Set axis span and label + * $graph->axis->min = 0; + * $graph->axis->max = 100; + * $graph->axis->label = 'Coverage '; + * + * $graph->render( 400, 150, 'custom_odometer_chart.svg' ); + * + * + * Each chart consists of several chart elements which represents logical parts + * of the chart and can be formatted independently. The odometer chart consists + * of: + * - title ( {@link ezcGraphChartElementText} ) + * - background ( {@link ezcGraphChartElementBackground} ) + * + * All elements can be configured by accessing them as properties of the chart: + * + * + * $chart->title->position = ezcGraph::BOTTOM; + * + * + * The chart itself also offers several options to configure the appearance. + * The extended configure options are available in + * {@link ezcGraphOdometerChartOptions} extending the {@link + * ezcGraphChartOptions}. + * + * @property ezcGraphOdometerChartOptions $options + * Chart options class + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphOdometerChart extends ezcGraphChart +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphOdometerChartOptions( $options ); + + parent::__construct( $options ); + + $this->data = new ezcGraphChartSingleDataContainer( $this ); + + $this->addElement( 'axis', new ezcGraphChartElementNumericAxis()); + $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer(); + $this->elements['axis']->axisLabelRenderer->showZeroValue = true; + $this->elements['axis']->position = ezcGraph::LEFT; + $this->elements['axis']->axisSpace = .05; + } + + /** + * Property write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param string $propertyName Option name + * @param mixed $propertyValue Option value; + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'axis': + if ( $propertyValue instanceof ezcGraphChartElementAxis ) + { + $this->addElement( 'axis', $propertyValue ); + $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer(); + $this->elements['axis']->axisLabelRenderer->showZeroValue = true; + $this->elements['axis']->position = ezcGraph::LEFT; + $this->elements['axis']->axisSpace = .05; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); + } + break; + case 'renderer': + if ( $propertyValue instanceof ezcGraphOdometerRenderer ) + { + parent::__set( $propertyName, $propertyValue ); + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphOdometerRenderer' ); + } + break; + default: + parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render the assigned data + * + * Will renderer all charts data in the remaining boundings after drawing + * all other chart elements. The data will be rendered depending on the + * settings in the dataset. + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Remaining boundings + * @return void + */ + protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + // Draw the odometer data + $dataset = $this->data->rewind(); + + foreach ( $dataset as $key => $value ) + { + $renderer->drawOdometerMarker( + $boundings, + $this->elements['axis']->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + $this->elements['axis']->getCoordinate( $value ), + 0 + ) + ), + $dataset->symbol[$key], + $dataset->color[$key], + $this->options->markerWidth + ); + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + public function getDefaultDisplayType() + { + return ezcGraph::ODOMETER; + } + + /** + * Renders the basic elements of this chart type + * + * @param int $width + * @param int $height + * @return void + */ + protected function renderElements( $width, $height ) + { + if ( !count( $this->data ) ) + { + throw new ezcGraphNoDataException(); + } + + // Set image properties in driver + $this->driver->options->width = $width; + $this->driver->options->height = $height; + + // no legend + $this->renderElement['legend'] = false; + + // Get boundings from parameters + $this->options->width = $width; + $this->options->height = $height; + + $boundings = new ezcGraphBoundings(); + $boundings->x1 = $this->options->width; + $boundings->y1 = $this->options->height; + + // Get values out the single used dataset to calculate axis boundings + $values = array(); + foreach ( $this->data->rewind() as $value ) + { + $values[] = $value; + } + + // Set values for Axis + $this->elements['axis']->addData( $values ); + $this->elements['axis']->nullPosition = 0.5 + $this->options->odometerHeight / 2; + $this->elements['axis']->calculateAxisBoundings(); + + // Render subelements exept axis, which will be drawn together with the + // odometer bar + foreach ( $this->elements as $name => $element ) + { + // Skip element, if it should not get rendered + if ( $this->renderElement[$name] === false || + $name === 'axis' ) + { + continue; + } + + $this->driver->options->font = $element->font; + $boundings = $element->render( $this->renderer, $boundings ); + } + + // Draw basic odometer + $this->driver->options->font = $this->elements['axis']->font; + $boundings = $this->renderer->drawOdometer( + $boundings, + $this->elements['axis'], + $this->options + ); + + // Render graph + $this->renderData( $this->renderer, $boundings ); + } + + /** + * Render the pie chart + * + * Renders the chart into a file or stream. The width and height are + * needed to specify the dimensions of the resulting image. For direct + * output use 'php://stdout' as output file. + * + * @param int $width Image width + * @param int $height Image height + * @param string $file Output file + * @apichange + * @return void + */ + public function render( $width, $height, $file = null ) + { + $this->renderElements( $width, $height ); + + if ( !empty( $file ) ) + { + $this->renderer->render( $file ); + } + + $this->renderedFile = $file; + } + + /** + * Renders this chart to direct output + * + * Does the same as ezcGraphChart::render(), but renders directly to + * output and not into a file. + * + * @param int $width + * @param int $height + * @apichange + * @return void + */ + public function renderToOutput( $width, $height ) + { + // @TODO: merge this function with render an deprecate ommit of third + // argument in render() when API break is possible + $this->renderElements( $width, $height ); + $this->renderer->render( null ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/charts/pie.php b/src/TUnit/external/ezc/Graph/charts/pie.php new file mode 100644 index 0000000..ac189ec --- /dev/null +++ b/src/TUnit/external/ezc/Graph/charts/pie.php @@ -0,0 +1,308 @@ + + * // Create a new pie chart + * $chart = new ezcGraphPieChart(); + * + * // Add data to line chart + * $chart->data['sample dataset'] = new ezcGraphArrayDataSet( + * array( + * 'one' => 1.2, + * 'two' => 43.2, + * 'three' => -34.14, + * 'four' => 65, + * 'five' => 123, + * ) + * ); + * + * // Render chart with default 2d renderer and default SVG driver + * $chart->render( 500, 200, 'pie_chart.svg' ); + * + * + * Each chart consists of several chart elements which represents logical + * parts of the chart and can be formatted independently. The pie chart + * consists of: + * - title ( {@link ezcGraphChartElementText} ) + * - legend ( {@link ezcGraphChartElementLegend} ) + * - background ( {@link ezcGraphChartElementBackground} ) + * + * All elements can be configured by accessing them as properties of the chart: + * + * + * $chart->legend->position = ezcGraph::RIGHT; + * + * + * The chart itself also offers several options to configure the appearance. + * The extended configure options are available in + * {@link ezcGraphPieChartOptions} extending the {@link ezcGraphChartOptions}. + * + * @property ezcGraphPieChartOptions $options + * Chart options class + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphPieChart extends ezcGraphChart +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphPieChartOptions( $options ); + + parent::__construct( $options ); + + $this->data = new ezcGraphChartSingleDataContainer( $this ); + } + + /** + * Render the assigned data + * + * Will renderer all charts data in the remaining boundings after drawing + * all other chart elements. The data will be rendered depending on the + * settings in the dataset. + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Remaining boundings + * @return void + */ + protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + // Only draw the first (and only) dataset + $dataset = $this->data->rewind(); + $datasetName = $this->data->key(); + + $this->driver->options->font = $this->options->font; + + // Calculate sum of all values to be able to calculate percentage + $sum = 0; + foreach ( $dataset as $name => $value ) + { + if ( $value < 0 ) + { + throw new ezcGraphInvalidDataException( "Values >= 0 required, '$name' => '$value'." ); + } + + $sum += $value; + } + if ( $this->options->sum !== false ) + { + $sum = max( $sum, $this->options->sum ); + } + + if ( $sum <= 0 ) + { + throw new ezcGraphInvalidDataException( "Pie charts require a value sum > 0, your value: '$sum'." ); + } + + $angle = 0; + foreach ( $dataset as $label => $value ) + { + // Skip rendering values which equals 0 + if ( $value <= 0 ) + { + continue; + } + + switch ( $dataset->displayType->default ) + { + case ezcGraph::PIE: + $displayLabel = ( $this->options->labelCallback !== null + ? call_user_func( $this->options->labelCallback, $label, $value, $value / $sum ) + : sprintf( $this->options->label, $label, $value, $value / $sum * 100 ) ); + + $renderer->drawPieSegment( + $boundings, + new ezcGraphContext( $datasetName, $label, $dataset->url[$label] ), + $dataset->color[$label], + $angle, + $angle += $value / $sum * 360, + $displayLabel, + $dataset->highlight[$label] + ); + break; + default: + throw new ezcGraphInvalidDisplayTypeException( $dataset->displayType->default ); + break; + } + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + public function getDefaultDisplayType() + { + return ezcGraph::PIE; + } + + /** + * Apply tresh hold + * + * Iterates over the dataset and applies the configured tresh hold to + * the datasets data. + * + * @return void + */ + protected function applyThreshold() + { + if ( $this->options->percentThreshold || $this->options->absoluteThreshold ) + { + $dataset = $this->data->rewind(); + + $sum = 0; + foreach ( $dataset as $value ) + { + $sum += $value; + } + if ( $this->options->sum !== false ) + { + $sum = max( $sum, $this->options->sum ); + } + + $unset = array(); + foreach ( $dataset as $label => $value ) + { + if ( $label === $this->options->summarizeCaption ) + { + continue; + } + + if ( ( $value <= $this->options->absoluteThreshold ) || + ( ( $value / $sum ) <= $this->options->percentThreshold ) ) + { + if ( !isset( $dataset[$this->options->summarizeCaption] ) ) + { + $dataset[$this->options->summarizeCaption] = $value; + } + else + { + $dataset[$this->options->summarizeCaption] += $value; + } + + $unset[] = $label; + } + } + + foreach ( $unset as $label ) + { + unset( $dataset[$label] ); + } + } + } + + /** + * Renders the basic elements of this chart type + * + * @param int $width + * @param int $height + * @return void + */ + protected function renderElements( $width, $height ) + { + if ( !count( $this->data ) ) + { + throw new ezcGraphNoDataException(); + } + + // Set image properties in driver + $this->driver->options->width = $width; + $this->driver->options->height = $height; + + // Apply tresh hold + $this->applyThreshold(); + + // Generate legend + $this->elements['legend']->generateFromDataSet( $this->data->rewind() ); + + // Get boundings from parameters + $this->options->width = $width; + $this->options->height = $height; + + $boundings = new ezcGraphBoundings(); + $boundings->x1 = $this->options->width; + $boundings->y1 = $this->options->height; + + // Render subelements + foreach ( $this->elements as $name => $element ) + { + // Skip element, if it should not get rendered + if ( $this->renderElement[$name] === false ) + { + continue; + } + + $this->driver->options->font = $element->font; + $boundings = $element->render( $this->renderer, $boundings ); + } + + // Render graph + $this->renderData( $this->renderer, $boundings ); + } + + /** + * Render the pie chart + * + * Renders the chart into a file or stream. The width and height are + * needed to specify the dimensions of the resulting image. For direct + * output use 'php://stdout' as output file. + * + * @param int $width Image width + * @param int $height Image height + * @param string $file Output file + * @apichange + * @return void + */ + public function render( $width, $height, $file = null ) + { + $this->renderElements( $width, $height ); + + if ( !empty( $file ) ) + { + $this->renderer->render( $file ); + } + + $this->renderedFile = $file; + } + + /** + * Renders this chart to direct output + * + * Does the same as ezcGraphChart::render(), but renders directly to + * output and not into a file. + * + * @param int $width + * @param int $height + * @apichange + * @return void + */ + public function renderToOutput( $width, $height ) + { + // @TODO: merge this function with render an deprecate ommit of third + // argument in render() when API break is possible + $this->renderElements( $width, $height ); + $this->renderer->render( null ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/charts/radar.php b/src/TUnit/external/ezc/Graph/charts/radar.php new file mode 100644 index 0000000..adc0378 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/charts/radar.php @@ -0,0 +1,457 @@ + + * // Create a new radar chart + * $chart = new ezcGraphRadarChart(); + * + * // Add data to line chart + * $chart->data['sample dataset'] = new ezcGraphArrayDataSet( + * array( + * '100' => 1.2, + * '200' => 43.2, + * '300' => -34.14, + * '350' => 65, + * '400' => 123, + * ) + * ); + * + * // Render chart with default 2d renderer and default SVG driver + * $chart->render( 500, 200, 'radar_chart.svg' ); + * + * + * Each chart consists of several chart elements which represents logical + * parts of the chart and can be formatted independently. The line chart + * consists of: + * - title ( {@link ezcGraphChartElementText} ) + * - legend ( {@link ezcGraphChartElementLegend} ) + * - background ( {@link ezcGraphChartElementBackground} ) + * - axis ( {@link ezcGraphChartElementNumericAxis} ) + * - ratation axis ( {@link ezcGraphChartElementLabeledAxis} ) + * + * The type of the axis may be changed and all elements can be configured by + * accessing them as properties of the chart: + * + * The chart itself also offers several options to configure the appearance. + * The extended configure options are available in + * {@link ezcGraphRadarChartOptions} extending the + * {@link ezcGraphChartOptions}. + * + * + * $chart->legend->position = ezcGraph::RIGHT; + * + * + * @property ezcGraphRadarChartOptions $options + * Chart options class + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphRadarChart extends ezcGraphChart +{ + /** + * Store major grid color for child axis. + * + * @var ezcGraphColor + */ + protected $childAxisColor; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphRadarChartOptions( $options ); + $this->options->highlightFont = $this->options->font; + + parent::__construct(); + + $this->elements['rotationAxis'] = new ezcGraphChartElementLabeledAxis(); + + $this->addElement( 'axis', new ezcGraphChartElementNumericAxis() ); + $this->elements['axis']->position = ezcGraph::BOTTOM; + $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer(); + $this->elements['axis']->axisLabelRenderer->outerStep = true; + + $this->addElement( 'rotationAxis', new ezcGraphChartElementLabeledAxis() ); + + // Do not render axis with default method, because we need an axis for + // each label in dataset + $this->renderElement['axis'] = false; + $this->renderElement['rotationAxis'] = false; + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->childAxisColor = $palette->majorGridColor; + + parent::setFromPalette( $palette ); + } + + /** + * Property write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param string $propertyName Option name + * @param mixed $propertyValue Option value; + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'axis': + if ( $propertyValue instanceof ezcGraphChartElementAxis ) + { + $this->addElement( 'axis', $propertyValue ); + $this->elements['axis']->position = ezcGraph::BOTTOM; + $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer(); + $this->renderElement['axis'] = false; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); + } + break; + case 'rotationAxis': + if ( $propertyValue instanceof ezcGraphChartElementAxis ) + { + $this->addElement( 'rotationAxis', $propertyValue ); + $this->renderElement['rotationAxis'] = false; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' ); + } + break; + case 'renderer': + if ( $propertyValue instanceof ezcGraphRadarRenderer ) + { + parent::__set( $propertyName, $propertyValue ); + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphRadarRenderer' ); + } + break; + default: + parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Draws a single rotated axis + * + * Sets the axis label position depending on the axis rotation. + * + * @param ezcGraphChartElementAxis $axis + * @param ezcGraphBoundings $boundings + * @param ezcGraphCoordinate $center + * @param float $position + * @param float $lastPosition + * @return void + */ + protected function drawRotatedAxis( ezcGraphChartElementAxis $axis, ezcGraphBoundings $boundings, ezcGraphCoordinate $center, $position, $lastPosition = null ) + { + // Set axis position depending on angle for better axis label + // positioning + $angle = $position * 2 * M_PI; + switch ( (int) ( ( $position + .125 ) * 4 ) ) + { + case 0: + case 4: + $axis->position = ezcGraph::BOTTOM; + break; + case 1: + $axis->position = ezcGraph::LEFT; + break; + case 2: + $axis->position = ezcGraph::TOP; + break; + case 3: + $axis->position = ezcGraph::RIGHT; + break; + } + + // Set last step to correctly draw grid + if ( $axis->axisLabelRenderer instanceof ezcGraphAxisRadarLabelRenderer ) + { + $axis->axisLabelRenderer->lastStep = $lastPosition; + } + + // Do not draw axis label for last step + if ( abs( $position - 1 ) <= .001 ) + { + $axis->label = null; + } + + $this->renderer->drawAxis( + $boundings, + clone $center, + $dest = new ezcGraphCoordinate( + $center->x + sin( $angle ) * ( $boundings->width / 2 ), + $center->y - cos( $angle ) * ( $boundings->height / 2 ) + ), + clone $axis, + clone $axis->axisLabelRenderer + ); + } + + /** + * Render the assigned data + * + * Will renderer all charts data in the remaining boundings after drawing + * all other chart elements. The data will be rendered depending on the + * settings in the dataset. + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Remaining boundings + * @return void + */ + protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + // Apply axis space + $xAxisSpace = ( $boundings->x1 - $boundings->x0 ) * $this->axis->axisSpace; + $yAxisSpace = ( $boundings->y1 - $boundings->y0 ) * $this->axis->axisSpace; + + $center = new ezcGraphCoordinate( + ( $boundings->width / 2 ), + ( $boundings->height / 2 ) + ); + + // We do not differentiate between display types in radar charts. + $nr = $count = count( $this->data ); + + // Draw axis at major steps of virtual axis + $steps = $this->elements['rotationAxis']->getSteps(); + $lastStepPosition = null; + $axisColor = $this->elements['axis']->border; + foreach ( $steps as $step ) + { + $this->elements['axis']->label = $step->label; + $this->drawRotatedAxis( $this->elements['axis'], $boundings, $center, $step->position, $lastStepPosition ); + $lastStepPosition = $step->position; + + if ( count( $step->childs ) ) + { + foreach ( $step->childs as $childStep ) + { + $this->elements['axis']->label = null; + $this->elements['axis']->border = $this->childAxisColor; + + $this->drawRotatedAxis( $this->elements['axis'], $boundings, $center, $childStep->position, $lastStepPosition ); + $lastStepPosition = $childStep->position; + } + } + + $this->elements['axis']->border = $axisColor; + } + + // Display data + $this->elements['axis']->position = ezcGraph::TOP; + foreach ( $this->data as $datasetName => $data ) + { + --$nr; + // Determine fill color for dataset + if ( $this->options->fillLines !== false ) + { + $fillColor = clone $data->color->default; + $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) ); + } + else + { + $fillColor = null; + } + + // Draw lines for dataset + $lastPoint = false; + foreach ( $data as $key => $value ) + { + $point = new ezcGraphCoordinate( + $this->elements['rotationAxis']->getCoordinate( $key ), + $this->elements['axis']->getCoordinate( $value ) + ); + + /* Transformation required for 3d like renderers ... + * which axis should transform here? + $point = $this->elements['xAxis']->axisLabelRenderer->modifyChartDataPosition( + $this->elements['yAxis']->axisLabelRenderer->modifyChartDataPosition( + new ezcGraphCoordinate( + $this->elements['xAxis']->getCoordinate( $key ), + $this->elements['yAxis']->getCoordinate( $value ) + ) + ) + ); + // */ + + $renderer->drawRadarDataLine( + $boundings, + new ezcGraphContext( $datasetName, $key, $data->url[$key] ), + $data->color->default, + clone $center, + ( $lastPoint === false ? $point : $lastPoint ), + $point, + $nr, + $count, + $data->symbol[$key], + $data->color[$key], + $fillColor, + $this->options->lineThickness + ); + + $lastPoint = $point; + } + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + public function getDefaultDisplayType() + { + return ezcGraph::LINE; + } + + /** + * Renders the basic elements of this chart type + * + * @param int $width + * @param int $height + * @return void + */ + protected function renderElements( $width, $height ) + { + if ( !count( $this->data ) ) + { + throw new ezcGraphNoDataException(); + } + + // Set image properties in driver + $this->driver->options->width = $width; + $this->driver->options->height = $height; + + // Calculate axis scaling and labeling + foreach ( $this->data as $dataset ) + { + $labels = array(); + $values = array(); + foreach ( $dataset as $label => $value ) + { + $labels[] = $label; + $values[] = $value; + } + + $this->elements['axis']->addData( $values ); + $this->elements['rotationAxis']->addData( $labels ); + } + + $this->elements['axis']->calculateAxisBoundings(); + $this->elements['rotationAxis']->calculateAxisBoundings(); + + // Generate legend + $this->elements['legend']->generateFromDataSets( $this->data ); + + // Get boundings from parameters + $this->options->width = $width; + $this->options->height = $height; + + // Render subelements + $boundings = new ezcGraphBoundings(); + $boundings->x1 = $this->options->width; + $boundings->y1 = $this->options->height; + + // Render subelements + foreach ( $this->elements as $name => $element ) + { + // Skip element, if it should not get rendered + if ( $this->renderElement[$name] === false ) + { + continue; + } + + $this->driver->options->font = $element->font; + $boundings = $element->render( $this->renderer, $boundings ); + } + + // Render graph + $this->renderData( $this->renderer, $boundings ); + } + + /** + * Render the line chart + * + * Renders the chart into a file or stream. The width and height are + * needed to specify the dimensions of the resulting image. For direct + * output use 'php://stdout' as output file. + * + * @param int $width Image width + * @param int $height Image height + * @param string $file Output file + * @apichange + * @return void + */ + public function render( $width, $height, $file = null ) + { + $this->renderElements( $width, $height ); + + if ( !empty( $file ) ) + { + $this->renderer->render( $file ); + } + + $this->renderedFile = $file; + } + + /** + * Renders this chart to direct output + * + * Does the same as ezcGraphChart::render(), but renders directly to + * output and not into a file. + * + * @param int $width + * @param int $height + * @apichange + * @return void + */ + public function renderToOutput( $width, $height ) + { + // @TODO: merge this function with render an deprecate ommit of third + // argument in render() when API break is possible + $this->renderElements( $width, $height ); + $this->renderer->render( null ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/colors/color.php b/src/TUnit/external/ezc/Graph/colors/color.php new file mode 100644 index 0000000..d310cca --- /dev/null +++ b/src/TUnit/external/ezc/Graph/colors/color.php @@ -0,0 +1,290 @@ +properties['red'] = 0; + $this->properties['green'] = 0; + $this->properties['blue'] = 0; + $this->properties['alpha'] = 0; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'red': + case 'green': + case 'blue': + case 'alpha': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 255 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 255' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * Creates an ezcGraphColor object from a hexadecimal color representation + * + * @param mixed $string Hexadecimal color representation + * @return ezcGraphColor + */ + static public function fromHex( $string ) + { + // Remove trailing # + if ( $string[0] === '#' ) + { + $string = substr( $string, 1 ); + } + + // Iterate over chunks and convert to integer + $color = new ezcGraphColor(); + $keys = array( 'red', 'green', 'blue', 'alpha' ); + foreach ( str_split( $string, 2) as $nr => $hexValue ) + { + if ( isset( $keys[$nr] ) ) + { + $key = $keys[$nr]; + $color->$key = hexdec( $hexValue ) % 256; + } + } + + // Set missing values to zero + for ( ++$nr; $nr < count( $keys ); ++$nr ) + { + $key = $keys[$nr]; + $color->$key = 0; + } + + return $color; + } + + /** + * Creates an ezcGraphColor object from an array of integers + * + * @param array $array Array of integer color values + * @return ezcGraphColor + */ + static public function fromIntegerArray( array $array ) + { + // Iterate over array elements + $color = new ezcGraphColor(); + $keys = array( 'red', 'green', 'blue', 'alpha' ); + $nr = 0; + foreach ( $array as $colorValue ) + { + if ( isset( $keys[$nr] ) ) + { + $key = $keys[$nr++]; + $color->$key = ( (int) $colorValue ) % 256; + } + } + + // Set missing values to zero + for ( $nr; $nr < count( $keys ); ++$nr ) + { + $key = $keys[$nr]; + $color->$key = 0; + } + + return $color; + } + + /** + * Creates an ezcGraphColor object from an array of floats + * + * @param array $array Array of float color values + * @return ezcGraphColor + */ + static public function fromFloatArray( array $array ) + { + // Iterate over array elements + $color = new ezcGraphColor(); + $keys = array( 'red', 'green', 'blue', 'alpha' ); + $nr = 0; + foreach ( $array as $colorValue ) + { + if ( isset( $keys[$nr] ) ) + { + $key = $keys[$nr++]; + $color->$key = ( (float) $colorValue * 255 ) % 256; + } + } + + // Set missing values to zero + for ( $nr; $nr < count( $keys ); ++$nr ) + { + $key = $keys[$nr]; + $color->$key = 0; + } + + return $color; + } + + /** + * Tries to parse provided color value + * + * This method can be used to create a color struct from arbritrary color + * representations. The following values are accepted + * + * - Hexadecimal color definitions, like known from HTML, CSS and SVG + * + * Color definitions like #FF0000, with and and without number sign, + * where each pair of bytes is interpreted as a color value for the + * channels RGB(A). These color values may contain up to 4 values, where + * the last value is considered as the alpha channel. + * + * - Array of integers + * + * If an array of integers is provided as input teh value in each channel + * may be in the span [0 - 255] and is assigned to the color channels + * RGB(A). Up to four values are used from the array. + * + * - Array of floats + * + * If an array of floats is provided as input teh value in each channel + * may be in the span [0 - 1] and is assigned to the color channels + * RGB(A). Up to four values are used from the array. + * + * @param mixed $color Some kind of color definition + * @return ezcGraphColor + */ + static public function create( $color ) + { + if ( $color instanceof ezcGraphColor ) + { + return $color; + } + elseif ( is_string( $color ) ) + { + return ezcGraphColor::fromHex( $color ); + } + elseif ( is_array( $color ) ) + { + $testElement = reset( $color ); + if ( is_int( $testElement ) ) + { + return ezcGraphColor::fromIntegerArray( $color ); + } + else + { + return ezcGraphColor::fromFloatArray( $color ); + } + } + else + { + throw new ezcGraphUnknownColorDefinitionException( $color ); + } + } + + /** + * Returns a copy of the current color made more transparent by the given + * factor + * + * @param mixed $value Percent to make color mor transparent + * @return ezcGraphColor New color + */ + public function transparent( $value ) + { + $color = clone $this; + + $color->alpha = 255 - (int) round( ( 255 - $this->alpha ) * ( 1 - $value ) ); + + return $color; + } + + /** + * Inverts and returns a copy of the current color + * + * @return ezcGraphColor New Color + */ + public function invert() + { + $color = new ezcGraphColor(); + + $color->red = 255 - $this->red; + $color->green = 255 - $this->green; + $color->blue = 255 - $this->blue; + $color->alpha = $this->alpha; + + return $color; + } + + /** + * Returns a copy of the current color darkened by the given factor + * + * @param float $value Percent to darken the color + * @return ezcGraphColor New color + */ + public function darken( $value ) + { + $color = clone $this; + + $value = 1 - $value; + $color->red = min( 255, max( 0, (int) round( $this->red * $value ) ) ); + $color->green = min( 255, max( 0, (int) round( $this->green * $value ) ) ); + $color->blue = min( 255, max( 0, (int) round( $this->blue * $value ) ) ); + + return $color; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/colors/linear_gradient.php b/src/TUnit/external/ezc/Graph/colors/linear_gradient.php new file mode 100644 index 0000000..5b51797 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/colors/linear_gradient.php @@ -0,0 +1,147 @@ +properties['startColor'] = $startColor; + $this->properties['endColor'] = $endColor; + $this->properties['startPoint'] = $startPoint; + $this->properties['endPoint'] = $endPoint; + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'startPoint': + if ( !$propertyValue instanceof ezcGraphCoordinate ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' ); + } + else + { + $this->properties['startPoint'] = $propertyValue; + } + break; + case 'endPoint': + if ( !$propertyValue instanceof ezcGraphCoordinate ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' ); + } + else + { + $this->properties['endPoint'] = $propertyValue; + } + break; + case 'startColor': + $this->properties['startColor'] = ezcGraphColor::create( $propertyValue ); + break; + case 'endColor': + $this->properties['endColor'] = ezcGraphColor::create( $propertyValue ); + break; + } + } + + /** + * __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 'red': + case 'green': + case 'blue': + case 'alpha': + // Fallback to native color + return $this->properties['startColor']->$propertyName; + default: + if ( isset( $this->properties[$propertyName] ) ) + { + return $this->properties[$propertyName]; + } + else + { + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + } + + /** + * Returns a unique string representation for the gradient. + * + * @access public + * @return void + */ + public function __toString() + { + return sprintf( 'LinearGradient_%d_%d_%d_%d_%02x%02x%02x%02x_%02x%02x%02x%02x', + $this->properties['startPoint']->x, + $this->properties['startPoint']->y, + $this->properties['endPoint']->x, + $this->properties['endPoint']->y, + $this->properties['startColor']->red, + $this->properties['startColor']->green, + $this->properties['startColor']->blue, + $this->properties['startColor']->alpha, + $this->properties['endColor']->red, + $this->properties['endColor']->green, + $this->properties['endColor']->blue, + $this->properties['endColor']->alpha + ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/colors/radial_gradient.php b/src/TUnit/external/ezc/Graph/colors/radial_gradient.php new file mode 100644 index 0000000..2d046cb --- /dev/null +++ b/src/TUnit/external/ezc/Graph/colors/radial_gradient.php @@ -0,0 +1,173 @@ +properties['center'] = $center; + $this->properties['width'] = (float) $width; + $this->properties['height'] = (float) $height; + $this->properties['offset'] = 0; + $this->properties['startColor'] = $startColor; + $this->properties['endColor'] = $endColor; + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'center': + if ( !$propertyValue instanceof ezcGraphCoordinate ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' ); + } + else + { + $this->properties['center'] = $propertyValue; + } + break; + case 'width': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['width'] = (float) $propertyValue; + break; + case 'height': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['height'] = (float) $propertyValue; + break; + case 'offset': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['offset'] = $propertyValue; + break; + case 'startColor': + $this->properties['startColor'] = ezcGraphColor::create( $propertyValue ); + break; + case 'endColor': + $this->properties['endColor'] = ezcGraphColor::create( $propertyValue ); + break; + } + } + + /** + * __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 'red': + case 'green': + case 'blue': + case 'alpha': + // Fallback to native color + return $this->properties['startColor']->$propertyName; + default: + if ( isset( $this->properties[$propertyName] ) ) + { + return $this->properties[$propertyName]; + } + else + { + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + } + + /** + * Returns a unique string representation for the gradient. + * + * @access public + * @return void + */ + public function __toString() + { + return sprintf( 'RadialGradient_%d_%d_%d_%d_%.2f_%02x%02x%02x%02x_%02x%02x%02x%02x', + $this->properties['center']->x, + $this->properties['center']->y, + $this->properties['width'], + $this->properties['height'], + $this->properties['offset'], + $this->properties['startColor']->red, + $this->properties['startColor']->green, + $this->properties['startColor']->blue, + $this->properties['startColor']->alpha, + $this->properties['endColor']->red, + $this->properties['endColor']->green, + $this->properties['endColor']->blue, + $this->properties['endColor']->alpha + ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/data_container/base.php b/src/TUnit/external/ezc/Graph/data_container/base.php new file mode 100644 index 0000000..dfe87b1 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/data_container/base.php @@ -0,0 +1,225 @@ +chart = $chart; + } + + /** + * Adds a dataset to the charts data + * + * @param string $name Name of dataset + * @param ezcGraphDataSet $dataSet + * @param mixed $values Values to create dataset with + * @throws ezcGraphTooManyDataSetExceptions + * If too many datasets are created + * @return ezcGraphDataSet + */ + protected function addDataSet( $name, ezcGraphDataSet $dataSet ) + { + $this->data[$name] = $dataSet; + + $this->data[$name]->label = $name; + $this->data[$name]->palette = $this->chart->palette; + $this->data[$name]->displayType = $this->chart->getDefaultDisplayType(); + } + + /** + * Returns if the given offset exists. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key Identifier of dataset. + * @return bool True when the offset exists, otherwise false. + */ + public function offsetExists( $key ) + { + return isset( $this->data[$key] ); + } + + /** + * Returns the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key Identifier of dataset. + * @return ezcGraphDataSet + * + * @throws ezcGraphNoSuchDataSetException + * If no dataset with identifier exists + */ + public function offsetGet( $key ) + { + if ( !isset( $this->data[$key] ) ) + { + throw new ezcGraphNoSuchDataSetException( $key ); + } + + return $this->data[$key]; + } + + /** + * Set the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key + * @param ezcGraphDataSet $value + * @return void + * + * @throws ezcBaseValueException + * If supplied value is not an ezcGraphDataSet + */ + public function offsetSet( $key, $value ) + { + if ( !$value instanceof ezcGraphDataSet ) + { + throw new ezcBaseValueException( $key, $value, 'ezcGraphDataSet' ); + } + + return $this->addDataSet( $key, $value ); + } + + /** + * Unset the element with the given offset. + * + * This method is part of the ArrayAccess interface to allow access to the + * data of this object as if it was an array. + * + * @param string $key + * @return void + */ + public function offsetUnset( $key ) + { + if ( !isset( $this->data[$key] ) ) + { + throw new ezcGraphNoSuchDataSetException( $key ); + } + + unset( $this->data[$key] ); + } + + /** + * Returns the currently selected dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return ezcGraphDataSet The currently selected dataset. + */ + public function current() + { + return current( $this->data ); + } + + /** + * Returns the next dataset and selects it or false on the last dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return mixed ezcGraphDataSet if the next dataset exists, or false. + */ + public function next() + { + return next( $this->data ); + } + + /** + * Returns the key of the currently selected dataset. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return int The key of the currently selected dataset. + */ + public function key() + { + return key( $this->data ); + } + + /** + * Returns if the current dataset is valid. + * + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return bool If the current dataset is valid + */ + public function valid() + { + return ( current( $this->data ) !== false ); + } + + /** + * Selects the very first dataset and returns it. + * This method is part of the Iterator interface to allow access to the + * datasets of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return ezcGraphDataSet The very first dataset. + */ + public function rewind() + { + return reset( $this->data ); + } + + /** + * Returns the number of datasets in the row. + * + * This method is part of the Countable interface to allow the usage of + * PHP's count() function to check how many datasets exist. + * + * @return int Number of datasets. + */ + public function count() + { + return count( $this->data ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/data_container/single.php b/src/TUnit/external/ezc/Graph/data_container/single.php new file mode 100644 index 0000000..d24b375 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/data_container/single.php @@ -0,0 +1,51 @@ +data ) >= 1 && + !isset( $this->data[$name] ) ) + { + throw new ezcGraphTooManyDataSetsExceptions( $name ); + } + else + { + parent::addDataSet( $name, $dataSet ); + + // Resette palette color counter + $this->chart->palette->resetColorCounter(); + + // Colorize each data element + foreach ( $this->data[$name] as $label => $value ) + { + $this->data[$name]->color[$label] = $this->chart->palette->dataSetColor; + } + } + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/datasets/array.php b/src/TUnit/external/ezc/Graph/datasets/array.php new file mode 100644 index 0000000..56cc186 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/array.php @@ -0,0 +1,71 @@ +createFromArray( $data ); + parent::__construct(); + } + + /** + * setData + * + * Can handle data provided through an array or iterator. + * + * @param array|Iterator $data + * @access public + * @return void + */ + protected function createFromArray( $data = array() ) + { + if ( !is_array( $data ) && + !( $data instanceof Traversable ) ) + { + throw new ezcGraphInvalidArrayDataSourceException( $data ); + } + + $this->data = array(); + foreach ( $data as $key => $value ) + { + $this->data[$key] = $value; + } + + if ( !count( $this->data ) ) + { + throw new ezcGraphInvalidDataException( 'Data sets should contain some values.' ); + } + } + + /** + * Returns the number of elements in this dataset + * + * @return int + */ + public function count() + { + return count( $this->data ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/datasets/average.php b/src/TUnit/external/ezc/Graph/datasets/average.php new file mode 100644 index 0000000..89f7c67 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/average.php @@ -0,0 +1,359 @@ +mixed) + */ + protected $properties; + + /** + * Constructor + * + * @param ezcGraphDataSet $dataset Dataset to interpolate + * @param int $order Maximum order of interpolating polynom + * @return void + * @ignore + */ + public function __construct( ezcGraphDataSet $dataset, $order = 3 ) + { + parent::__construct(); + + $this->properties['resolution'] = 100; + $this->properties['polynomOrder'] = (int) $order; + + $this->source = $dataset; + } + + /** + * Options write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param mixed $propertyName Option name + * @param mixed $propertyValue Option value; + * @return mixed + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'polynomOrder': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 0' ); + } + + $this->properties['polynomOrder'] = (int) $propertyValue; + $this->polynom = false; + break; + case 'resolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' ); + } + + $this->properties['resolution'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Property get access. + * Simply returns a given option. + * + * @param string $propertyName The name of the option to get. + * @return mixed The option value. + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + */ + public function __get( $propertyName ) + { + if ( array_key_exists( $propertyName, $this->properties ) ) + { + return $this->properties[$propertyName]; + } + return parent::__get( $propertyName ); + } + + /** + * Build the polynom based on the given points. + * + * @return void + */ + protected function buildPolynom() + { + $points = array(); + + foreach ( $this->source as $key => $value ) + { + if ( !is_numeric( $key ) ) + { + throw new ezcGraphDatasetAverageInvalidKeysException(); + } + + if ( ( $this->min === false ) || ( $this->min > $key ) ) + { + $this->min = (float) $key; + } + + if ( ( $this->max === false ) || ( $this->max < $key ) ) + { + $this->max = (float) $key; + } + + $points[] = new ezcGraphCoordinate( (float) $key, (float) $value ); + } + + // Build transposed and normal Matrix out of coordiantes + $a = new ezcGraphMatrix( count( $points ), $this->polynomOrder + 1 ); + $b = new ezcGraphMatrix( count( $points ), 1 ); + + for ( $i = 0; $i <= $this->properties['polynomOrder']; ++$i ) + { + foreach ( $points as $nr => $point ) + { + $a->set( $nr, $i, pow( $point->x, $i ) ); + $b->set( $nr, 0, $point->y ); + } + } + + $at = clone $a; + $at->transpose(); + + $left = $at->multiply( $a ); + $right = $at->multiply( $b ); + + $this->polynom = $left->solveNonlinearEquatation( $right ); + } + + /** + * Returns a polynom of the defined order witch matches the datapoints + * using the least squares algorithm. + * + * @return ezcGraphPolynom Polynom + */ + public function getPolynom() + { + if ( $this->polynom === false ) + { + $this->buildPolynom(); + } + + return $this->polynom; + } + + /** + * Get the x coordinate for the current position + * + * @param int $position Position + * @return float x coordinate + */ + protected function getKey() + { + $polynom = $this->getPolynom(); + return $this->min + + ( $this->max - $this->min ) / $this->resolution * $this->position; + } + + /** + * Returns true if the given datapoint exists + * Allows isset() using ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return bool Wether the key exists. + */ + public function offsetExists( $key ) + { + $polynom = $this->getPolynom(); + return ( ( $key >= $this->min ) && ( $key <= $this->max ) ); + } + + /** + * Returns the value for the given datapoint + * Get an datapoint value by ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return float The datapoint value. + */ + public function offsetGet( $key ) + { + $polynom = $this->getPolynom(); + return $polynom->evaluate( $key ); + } + + /** + * Throws a ezcBasePropertyPermissionException because single datapoints + * cannot be set in average datasets. + * + * @param string $key The kex of a datapoint to set. + * @param float $value The value for the datapoint. + * @throws ezcBasePropertyPermissionException + * Always, because access is readonly. + * @return void + */ + public function offsetSet( $key, $value ) + { + throw new ezcBasePropertyPermissionException( $key, ezcBasePropertyPermissionException::READ ); + } + + /** + * Returns the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The currently selected datapoint. + */ + final public function current() + { + $polynom = $this->getPolynom(); + return $polynom->evaluate( $this->getKey() ); + } + + /** + * Returns the next datapoint and selects it or false on the last datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float datapoint if it exists, or false. + */ + final public function next() + { + if ( ++$this->position >= $this->resolution ) + { + return false; + } + else + { + return $this->current(); + } + } + + /** + * Returns the key of the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The key of the currently selected datapoint. + */ + final public function key() + { + return (string) $this->getKey(); + } + + /** + * Returns if the current datapoint is valid. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return bool If the current datapoint is valid + */ + final public function valid() + { + $polynom = $this->getPolynom(); + + if ( $this->min >= $this->max ) + { + return false; + } + + return ( ( $this->getKey() >= $this->min ) && ( $this->getKey() <= $this->max ) ); + } + + /** + * Selects the very first datapoint and returns it. + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float The very first datapoint. + */ + final public function rewind() + { + $this->position = 0; + } + + /** + * Returns the number of elements in this dataset + * + * @return int + */ + public function count() + { + return $this->resolution; + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/datasets/base.php b/src/TUnit/external/ezc/Graph/datasets/base.php new file mode 100644 index 0000000..385f677 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/base.php @@ -0,0 +1,302 @@ +properties['label'] = new ezcGraphDataSetStringProperty( $this ); + $this->properties['color'] = new ezcGraphDataSetColorProperty( $this ); + $this->properties['symbol'] = new ezcGraphDataSetIntProperty( $this ); + $this->properties['lineThickness'] = new ezcGraphDataSetIntProperty( $this ); + $this->properties['highlight'] = new ezcGraphDataSetBooleanProperty( $this ); + $this->properties['highlightValue'] = new ezcGraphDataSetStringProperty( $this ); + $this->properties['displayType'] = new ezcGraphDataSetIntProperty( $this ); + $this->properties['url'] = new ezcGraphDataSetStringProperty( $this ); + + $this->properties['xAxis'] = new ezcGraphDataSetAxisProperty( $this ); + $this->properties['yAxis'] = new ezcGraphDataSetAxisProperty( $this ); + + $this->properties['highlight']->default = false; + } + + /** + * Options write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param mixed $propertyName Option name + * @param mixed $propertyValue Option value; + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'hilight': + $propertyName = 'highlight'; + case 'label': + case 'url': + case 'color': + case 'symbol': + case 'lineThickness': + case 'highlight': + case 'highlightValue': + case 'displayType': + case 'xAxis': + case 'yAxis': + $this->properties[$propertyName]->default = $propertyValue; + break; + + case 'palette': + $this->palette = $propertyValue; + $this->color->default = $this->palette->dataSetColor; + $this->symbol->default = $this->palette->dataSetSymbol; + break; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * Property get access. + * Simply returns a given option. + * + * @param string $propertyName The name of the option to get. + * @return mixed The option value. + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + */ + public function __get( $propertyName ) + { + if ( array_key_exists( $propertyName, $this->properties ) ) + { + return $this->properties[$propertyName]; + } + else + { + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Returns true if the given datapoint exists + * Allows isset() using ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return bool Wether the key exists. + */ + public function offsetExists( $key ) + { + return isset( $this->data[$key] ); + } + + /** + * Returns the value for the given datapoint + * Get an datapoint value by ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return float The datapoint value. + */ + public function offsetGet( $key ) + { + return $this->data[$key]; + } + + /** + * Sets the value for a datapoint. + * Sets an datapoint using ArrayAccess. + * + * @param string $key The kex of a datapoint to set. + * @param float $value The value for the datapoint. + * @return void + */ + public function offsetSet( $key, $value ) + { + $this->data[$key] = (float) $value; + } + + /** + * Unset an option. + * Unsets an option using ArrayAccess. + * + * @param string $key The options to unset. + * @return void + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @throws ezcBaseValueException + * If a the value for a property is out of range. + */ + public function offsetUnset( $key ) + { + unset( $this->data[$key] ); + } + + /** + * Returns the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The currently selected datapoint. + */ + public function current() + { + if ( !isset( $this->current ) ) + { + $this->keys = array_keys( $this->data ); + $this->current = 0; + } + + return $this->data[$this->keys[$this->current]]; + } + + /** + * Returns the next datapoint and selects it or false on the last datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float datapoint if it exists, or false. + */ + public function next() + { + if ( ++$this->current >= count( $this->keys ) ) + { + return false; + } + else + { + return $this->data[$this->keys[$this->current]]; + } + } + + /** + * Returns the key of the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The key of the currently selected datapoint. + */ + public function key() + { + return $this->keys[$this->current]; + } + + /** + * Returns if the current datapoint is valid. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return bool If the current datapoint is valid + */ + public function valid() + { + return isset( $this->keys[$this->current] ); + } + + /** + * Selects the very first datapoint and returns it. + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float The very first datapoint. + */ + public function rewind() + { + $this->keys = array_keys( $this->data ); + $this->current = 0; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/datasets/numeric.php b/src/TUnit/external/ezc/Graph/datasets/numeric.php new file mode 100644 index 0000000..11590c3 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/numeric.php @@ -0,0 +1,287 @@ +mixed) + */ + protected $properties; + + /** + * Constructor + * + * @param float $start Start value for x axis values of function + * @param float $end End value for x axis values of function + * @param callback $callback Callback function + * @return void + * @ignore + */ + public function __construct( $start = null, $end = null, $callback = null ) + { + parent::__construct(); + + $this->properties['start'] = null; + $this->properties['end'] = null; + $this->properties['callback'] = null; + + if ( $start !== null ) + { + $this->start = $start; + } + + if ( $end !== null ) + { + $this->end = $end; + } + + if ( $callback !== null ) + { + $this->callback = $callback; + } + + $this->properties['resolution'] = 100; + } + + /** + * Options write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param mixed $propertyName Option name + * @param mixed $propertyValue Option value; + * @return mixed + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'resolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' ); + } + + $this->properties['resolution'] = (int) $propertyValue; + break; + case 'start': + case 'end': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + case 'callback': + if ( !is_callable( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback' ); + } + + $this->properties[$propertyName] = $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Property get access. + * Simply returns a given option. + * + * @param string $propertyName The name of the option to get. + * @return mixed The option value. + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + */ + public function __get( $propertyName ) + { + if ( array_key_exists( $propertyName, $this->properties ) ) + { + return $this->properties[$propertyName]; + } + return parent::__get( $propertyName ); + } + + /** + * Get the x coordinate for the current position + * + * @param int $position Position + * @return float x coordinate + */ + protected function getKey() + { + return $this->start + + ( $this->end - $this->start ) / $this->resolution * $this->position; + } + + /** + * Returns true if the given datapoint exists + * Allows isset() using ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return bool Wether the key exists. + */ + public function offsetExists( $key ) + { + return ( ( $key >= $this->start ) && ( $key <= $this->end ) ); + } + + /** + * Returns the value for the given datapoint + * Get an datapoint value by ArrayAccess. + * + * @param string $key The key of the datapoint to get. + * @return float The datapoint value. + */ + public function offsetGet( $key ) + { + return call_user_func( $this->callback, $key ); + } + + /** + * Throws a ezcBasePropertyPermissionException because single datapoints + * cannot be set in average datasets. + * + * @param string $key The kex of a datapoint to set. + * @param float $value The value for the datapoint. + * @throws ezcBasePropertyPermissionException + * Always, because access is readonly. + * @return void + */ + public function offsetSet( $key, $value ) + { + throw new ezcBasePropertyPermissionException( $key, ezcBasePropertyPermissionException::READ ); + } + + /** + * Returns the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The currently selected datapoint. + */ + final public function current() + { + return call_user_func( $this->callback, $this->getKey() ); + } + + /** + * Returns the next datapoint and selects it or false on the last datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float datapoint if it exists, or false. + */ + final public function next() + { + if ( $this->start === $this->end ) + { + throw new ezcGraphDatasetAverageInvalidKeysException(); + } + + if ( ++$this->position >= $this->resolution ) + { + return false; + } + else + { + return $this->current(); + } + } + + /** + * Returns the key of the currently selected datapoint. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return string The key of the currently selected datapoint. + */ + final public function key() + { + return (string) $this->getKey(); + } + + /** + * Returns if the current datapoint is valid. + * + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return bool If the current datapoint is valid + */ + final public function valid() + { + return ( ( $this->getKey() >= $this->start ) && ( $this->getKey() <= $this->end ) ); + } + + /** + * Selects the very first datapoint and returns it. + * This method is part of the Iterator interface to allow access to the + * datapoints of this row by iterating over it like an array (e.g. using + * foreach). + * + * @return float The very first datapoint. + */ + final public function rewind() + { + $this->position = 0; + } + + /** + * Returns the number of elements in this dataset + * + * @return int + */ + public function count() + { + return $this->resolution + 1; + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/datasets/property/axis.php b/src/TUnit/external/ezc/Graph/datasets/property/axis.php new file mode 100644 index 0000000..bcb1c00 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/property/axis.php @@ -0,0 +1,62 @@ + diff --git a/src/TUnit/external/ezc/Graph/datasets/property/boolean.php b/src/TUnit/external/ezc/Graph/datasets/property/boolean.php new file mode 100644 index 0000000..ab4b9e8 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/property/boolean.php @@ -0,0 +1,37 @@ + diff --git a/src/TUnit/external/ezc/Graph/datasets/property/color.php b/src/TUnit/external/ezc/Graph/datasets/property/color.php new file mode 100644 index 0000000..2bea337 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/property/color.php @@ -0,0 +1,37 @@ + diff --git a/src/TUnit/external/ezc/Graph/datasets/property/integer.php b/src/TUnit/external/ezc/Graph/datasets/property/integer.php new file mode 100644 index 0000000..3eda50f --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/property/integer.php @@ -0,0 +1,37 @@ + diff --git a/src/TUnit/external/ezc/Graph/datasets/property/string.php b/src/TUnit/external/ezc/Graph/datasets/property/string.php new file mode 100644 index 0000000..69a0b13 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/datasets/property/string.php @@ -0,0 +1,37 @@ + diff --git a/src/TUnit/external/ezc/Graph/driver/cairo.php b/src/TUnit/external/ezc/Graph/driver/cairo.php new file mode 100644 index 0000000..7a90dfc --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/cairo.php @@ -0,0 +1,1010 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->background->color = '#FFFFFFFF'; + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->renderer = new ezcGraphRenderer3d(); + * $graph->renderer->options->pieChartShadowSize = 10; + * $graph->renderer->options->pieChartGleam = .5; + * $graph->renderer->options->dataBorder = false; + * $graph->renderer->options->pieChartHeight = 16; + * $graph->renderer->options->legendSymbolGleam = .5; + * + * // Use cairo driver + * $graph->driver = new ezcGraphCairoDriver(); + * + * $graph->render( 400, 200, 'tutorial_driver_cairo.png' ); + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphCairoDriver extends ezcGraphDriver +{ + /** + * Surface for cairo + * + * @var resource + */ + protected $surface; + + /** + * Current cairo context. + * + * @var resource + */ + protected $context; + + /** + * List of strings to draw + * array ( array( + * 'text' => array( 'strings' ), + * 'options' => ezcGraphFontOptions, + * ) + * + * @var array + */ + protected $strings = array(); + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'cairo_wrapper' ); + $this->options = new ezcGraphCairoDriverOptions( $options ); + } + + /** + * Initilize cairo surface + * + * Initilize cairo surface from values provided in the options object, if + * is has not been already initlized. + * + * @return void + */ + protected function initiliazeSurface() + { + // Immediatly exit, if surface already exists + if ( $this->surface !== null ) + { + return; + } + + $this->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, + $this->options->width, + $this->options->height + ); + + $this->context = cairo_create( $this->surface ); + cairo_set_line_width( $this->context, 1 ); + } + + /** + * Get SVG style definition + * + * Returns a string with SVG style definitions created from color, + * fillstatus and line thickness. + * + * @param ezcGraphColor $color Color + * @param mixed $filled Filled + * @param float $thickness Line thickness. + * @return string Formatstring + */ + protected function getStyle( ezcGraphColor $color, $filled = true, $thickness = 1. ) + { + switch ( true ) + { + case $color instanceof ezcGraphLinearGradient: + $pattern = cairo_pattern_create_linear( + $color->startPoint->x, $color->startPoint->y, + $color->endPoint->x, $color->endPoint->y + ); + + cairo_pattern_add_color_stop_rgba ( + $pattern, + 0, + $color->startColor->red / 255, + $color->startColor->green / 255, + $color->startColor->blue / 255, + 1 - $color->startColor->alpha / 255 + ); + + cairo_pattern_add_color_stop_rgba ( + $pattern, + 1, + $color->endColor->red / 255, + $color->endColor->green / 255, + $color->endColor->blue / 255, + 1 - $color->endColor->alpha / 255 + ); + + cairo_set_source( $this->context, $pattern ); + cairo_fill( $this->context ); + break; + + case $color instanceof ezcGraphRadialGradient: + $pattern = cairo_pattern_create_radial( + 0, 0, 0, + 0, 0, 1 + ); + + cairo_pattern_add_color_stop_rgba ( + $pattern, + 0, + $color->startColor->red / 255, + $color->startColor->green / 255, + $color->startColor->blue / 255, + 1 - $color->startColor->alpha / 255 + ); + + cairo_pattern_add_color_stop_rgba ( + $pattern, + 1, + $color->endColor->red / 255, + $color->endColor->green / 255, + $color->endColor->blue / 255, + 1 - $color->endColor->alpha / 255 + ); + + // Scale pattern, and move it to the correct position + $matrix = cairo_matrix_multiply( + $move = cairo_matrix_create_translate( -$color->center->x, -$color->center->y ), + $scale = cairo_matrix_create_scale( 1 / $color->width, 1 / $color->height ) + ); + cairo_pattern_set_matrix( $pattern, $matrix ); + + cairo_set_source( $this->context, $pattern ); + cairo_fill( $this->context ); + break; + default: + cairo_set_source_rgba( + $this->context, + $color->red / 255, + $color->green / 255, + $color->blue / 255, + 1 - $color->alpha / 255 + ); + break; + } + + // Set line width + cairo_set_line_width( $this->context, $thickness ); + + // Set requested fill state for context + if ( $filled ) + { + cairo_fill_preserve( $this->context ); + } + } + + /** + * 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. ) + { + $this->initiliazeSurface(); + + $path = cairo_new_path( $this->context ); + + $lastPoint = end( $points ); + cairo_move_to( $this->context, $lastPoint->x, $lastPoint->y ); + + foreach ( $points as $point ) + { + cairo_line_to( $this->context, $point->x, $point->y ); + } + + cairo_close_path( $this->context ); + + $this->getStyle( $color, $filled, $thickness ); + cairo_stroke( $this->context ); + + 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. ) + { + $this->initiliazeSurface(); + + $path = cairo_new_path( $this->context ); + + cairo_move_to( $this->context, $start->x, $start->y ); + cairo_line_to( $this->context, $end->x, $end->y ); + + $this->getStyle( $color, false, $thickness ); + cairo_stroke( $this->context ); + + return array( $start, $end ); + } + + /** + * 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 ) + { + cairo_select_font_face( $this->context, $font->name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL ); + cairo_set_font_size( $this->context, $size ); + $extents = cairo_text_extents( $this->context, $text ); + + return new ezcGraphBoundings( + 0, + 0, + $extents['width'], + $extents['height'] + ); + } + + /** + * 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 ) + { + $this->initiliazeSurface(); + + $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 ); + + $width -= $padding * 2; + $height -= $padding * 2; + $textPosition = new ezcGraphCoordinate( + $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; ) + { + $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size ); + if ( is_array( $result ) ) + { + break; + } + $size = ( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : floor( $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' => $textPosition, + '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 ), + ); + } + + /** + * Render text depending of font type and available font extensions + * + * @param string $id + * @param string $text + * @param string $font + * @param ezcGraphColor $color + * @param ezcGraphCoordinate $position + * @param float $size + * @param float $rotation + * @return void + */ + protected function renderText( $text, $font, ezcGraphColor $color, ezcGraphCoordinate $position, $size, $rotation = null ) + { + cairo_select_font_face( $this->context, $font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL ); + cairo_set_font_size( $this->context, $size ); + + // Store current state of context + cairo_save( $this->context ); + cairo_move_to( $this->context, 0, 0 ); + + if ( $rotation !== null ) + { + // Move to the center + cairo_translate( $this->context, + $rotation->getCenter()->x, + $rotation->getCenter()->y + ); + // Rotate around text center + cairo_rotate( $this->context, + deg2rad( $rotation->getRotation() ) + ); + // Center the text + cairo_translate( $this->context, + $position->x - $rotation->getCenter()->x, + $position->y - $rotation->getCenter()->y - $size * .15 + ); + } else { + cairo_translate( $this->context, + $position->x, + $position->y - $size * .15 + ); + } + + cairo_new_path( $this->context ); + $this->getStyle( $color, true ); + cairo_show_text( $this->context, $text ); + cairo_stroke( $this->context ); + + // Restore state of context + cairo_restore( $this->context ); + } + + /** + * 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() + { + $this->initiliazeSurface(); + + 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::CENTER ): + $xOffset = ( $text['width'] - $width ) / 2; + break; + case ( $text['align'] & ezcGraph::RIGHT ): + $xOffset = $text['width'] - $width; + break; + case ( $text['align'] & ezcGraph::LEFT ): + default: + $xOffset = 0; + 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 + $completeString = ''; + foreach ( $text['text'] as $line ) + { + $string = implode( ' ', $line ); + $completeString .= $string; + $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; + } + + // Optionally draw text shadow + if ( $text['font']->textShadow === true ) + { + $this->renderText( + $string, + $text['font']->name, + $text['font']->textShadowColor, + new ezcGraphCoordinate( + $position->x + $text['font']->textShadowOffset, + $position->y + $text['font']->textShadowOffset + ), + $size, + $text['rotation'] + ); + } + + // Finally draw text + $this->renderText( + $string, + $text['font']->name, + $text['font']->color, + $position, + $size, + $text['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 ) + { + $this->initiliazeSurface(); + + // Normalize angles + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + cairo_save( $this->context ); + + // Draw circular arc path + $path = cairo_new_path( $this->context ); + cairo_translate( $this->context, + $center->x, + $center->y + ); + cairo_scale( $this->context, + 1, $height / $width + ); + + cairo_move_to( $this->context, 0, 0 ); + cairo_arc( $this->context, + 0., 0., + $width / 2, + deg2rad( $startAngle ), + deg2rad( $endAngle ) + ); + cairo_line_to( $this->context, 0, 0 ); + + cairo_restore( $this->context ); + $this->getStyle( $color, $filled ); + cairo_stroke( $this->context ); + + // 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 circular arc consisting of several minor steps on the bounding + * lines. + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param mixed $size + * @param mixed $startAngle + * @param mixed $endAngle + * @param ezcGraphColor $color + * @param bool $filled + * @return string Element id + */ + protected function simulateCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled ) + { + for ( + $tmpAngle = min( ceil ( $startAngle / 180 ) * 180, $endAngle ); + $tmpAngle <= $endAngle; + $tmpAngle = min( ceil ( $startAngle / 180 + 1 ) * 180, $endAngle ) ) + { + $path = cairo_new_path( $this->context ); + cairo_move_to( $this->context, + $center->x + cos( deg2rad( $startAngle ) ) * $width / 2, + $center->y + sin( deg2rad( $startAngle ) ) * $height / 2 + ); + + // @TODO: Use cairo_curve_to() + for( + $angle = $startAngle; + $angle <= $tmpAngle; + $angle = min( $angle + $this->options->circleResolution, $tmpAngle ) ) + { + cairo_line_to( $this->context, + $center->x + cos( deg2rad( $angle ) ) * $width / 2, + $center->y + sin( deg2rad( $angle ) ) * $height / 2 + $size + ); + + if ( $angle === $tmpAngle ) + { + break; + } + } + + for( + $angle = $tmpAngle; + $angle >= $startAngle; + $angle = max( $angle - $this->options->circleResolution, $startAngle ) ) + { + cairo_line_to( $this->context, + $center->x + cos( deg2rad( $angle ) ) * $width / 2, + $center->y + sin( deg2rad( $angle ) ) * $height / 2 + ); + + if ( $angle === $startAngle ) + { + break; + } + } + + cairo_close_path( $this->context ); + $this->getStyle( $color, $filled ); + cairo_stroke( $this->context ); + + $startAngle = $tmpAngle; + if ( $tmpAngle === $endAngle ) + { + break; + } + } + } + + /** + * 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 ) + { + $this->initiliazeSurface(); + + // Normalize angles + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $color, $filled ); + + if ( ( $this->options->shadeCircularArc !== false ) && + $filled ) + { + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $center->x - $width, + $center->y + ), + new ezcGraphCoordinate( + $center->x + $width, + $center->y + ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ), + ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc * 1.5 ) + ); + + $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $gradient, $filled ); + } + + // 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 ) + { + $this->initiliazeSurface(); + + cairo_save( $this->context ); + + // Draw circular arc path + $path = cairo_new_path( $this->context ); + cairo_translate( $this->context, + $center->x, + $center->y + ); + cairo_scale( $this->context, + 1, $height / $width + ); + + cairo_arc( $this->context, + 0., 0., + $width / 2, + 0, 2 * M_PI + ); + + cairo_restore( $this->context ); + $this->getStyle( $color, $filled ); + cairo_stroke( $this->context ); + + // Create polygon array to return + $polygonArray = array(); + for ( $angle = 0; $angle < ( 2 * M_PI ); $angle += deg2rad( $this->options->imageMapResolution ) ) + { + $polygonArray[] = new ezcGraphCoordinate( + $center->x + + ( ( cos( $angle ) * $width ) / 2 ), + $center->y + + ( ( sin( $angle ) * $height ) / 2 ) + ); + } + + return $polygonArray; + } + + /** + * Draw an image + * + * The image will be inlined in the SVG document using data URL scheme. For + * this the mime type and base64 encoded file content will be merged to + * URL. + * + * @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->initiliazeSurface(); + + // Ensure given bitmap is a PNG image + $data = getimagesize( $file ); + if ( $data[2] !== IMAGETYPE_PNG ) + { + throw new Exception( 'Cairo only has support for PNGs.' ); + } + + // Create new surface from given bitmap + $imageSurface = cairo_image_surface_create_from_png( $file ); + + // Create pattern from source image to be able to transform it + $pattern = cairo_pattern_create_for_surface( $imageSurface ); + + // Scale pattern to defined dimensions and move it to its destination position + $matrix = cairo_matrix_multiply( + $move = cairo_matrix_create_translate( -$position->x, -$position->y ), + $scale = cairo_matrix_create_scale( $data[0] / $width, $data[1] / $height ) + ); + cairo_pattern_set_matrix( $pattern, $matrix ); + + // Merge surfaces + cairo_set_source( $this->context, $pattern ); + cairo_rectangle( $this->context, $position->x, $position->y, $width, $height ); + cairo_fill( $this->context ); + } + + /** + * Return mime type for current image format + * + * @return string + */ + public function getMimeType() + { + return 'image/png'; + } + + /** + * 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() + { + $this->drawAllTexts(); + + header( 'Content-Type: ' . $this->getMimeType() ); + + // Write to tmp file, echo and remove tmp file again. + $fileName = tempnam( '/tmp', 'ezc' ); + + // cairo_surface_write_to_png( $this->surface, $file ); + cairo_surface_write_to_png( $this->surface, $fileName ); + $contents = file_get_contents( $fileName ); + unlink( $fileName ); + + // Directly echo contents + echo $contents; + } + + /** + * Finally save image + * + * @param string $file Destination filename + * @return void + */ + public function render( $file ) + { + $this->drawAllTexts(); + cairo_surface_write_to_png( $this->surface, $file ); + } + + /** + * 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. + * + * This method returns an array, containing the surface and the context in + * a structure like: + * + * array( + * 'surface' => resource, + * 'context' => resource, + * ) + * + * + * @return array + */ + public function getResource() + { + return array( + 'surface' => $this->surface, + 'context' => $this->context, + ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/driver/flash.php b/src/TUnit/external/ezc/Graph/driver/flash.php new file mode 100644 index 0000000..9adde87 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/flash.php @@ -0,0 +1,972 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->driver = new ezcGraphFlashDriver(); + * $graph->options->font = 'tutorial_font.fdb'; + * + * $graph->driver->options->compression = 7; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->render( 400, 200, 'tutorial_driver_flash.swf' ); + * + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphFlashDriver extends ezcGraphDriver +{ + /** + * Flash movie + * + * @var SWFMovie + */ + protected $movie; + + /** + * Unique element id + * + * @var int + */ + protected $id = 1; + + /** + * Array with strings to draw later + * + * @var array + */ + protected $strings = array(); + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'ming' ); + $this->options = new ezcGraphFlashDriverOptions( $options ); + } + + /** + * Returns unique movie object as a parent canvas for all swf objects. + * + * @return SWFMovie + */ + public function getDocument() + { + if ( $this->movie === null ) + { + ming_setscale( 1.0 ); + $this->movie = new SWFMovie(); + $this->movie->setDimension( $this->modifyCoordinate( $this->options->width ), $this->modifyCoordinate( $this->options->height ) ); + $this->movie->setRate( 1 ); + $this->movie->setBackground( 255, 255, 255 ); + } + + return $this->movie; + } + + /** + * Set the fill and line properties for a SWWFShape according to the + * given parameters. + * + * @param SWFShape $shape + * @param ezcGraphColor $color + * @param mixed $thickness + * @param mixed $filled + * @return void + */ + protected function setShapeColor( SWFShape $shape, ezcGraphColor $color, $thickness, $filled ) + { + if ( $filled ) + { + switch ( true ) + { + case ( $color instanceof ezcGraphLinearGradient ): + $gradient = new SWFGradient(); + $gradient->addEntry( + 0, + $color->startColor->red, + $color->startColor->green, + $color->startColor->blue, + 255 - $color->startColor->alpha + ); + $gradient->addEntry( + 1, + $color->endColor->red, + $color->endColor->green, + $color->endColor->blue, + 255 - $color->endColor->alpha + ); + + $fill = $shape->addFill( $gradient, SWFFILL_LINEAR_GRADIENT ); + + // Calculate desired length of gradient + $length = sqrt( + pow( $color->endPoint->x - $color->startPoint->x, 2 ) + + pow( $color->endPoint->y - $color->startPoint->y, 2 ) + ); + + $fill->scaleTo( $this->modifyCoordinate( $length ) / 32768 , $this->modifyCoordinate( $length ) / 32768 ); + $fill->rotateTo( + rad2deg( asin( + ( $color->endPoint->x - $color->startPoint->x ) / $length + ) + 180 ) + ); + $fill->moveTo( + $this->modifyCoordinate( + ( $color->startPoint->x + $color->endPoint->x ) / 2 + ), + $this->modifyCoordinate( + ( $color->startPoint->y + $color->endPoint->y ) / 2 + ) + ); + + $shape->setLeftFill( $fill ); + break; + case ( $color instanceof ezcGraphRadialGradient ): + $gradient = new SWFGradient(); + $gradient->addEntry( + 0, + $color->startColor->red, + $color->startColor->green, + $color->startColor->blue, + 255 - $color->startColor->alpha + ); + $gradient->addEntry( + 1, + $color->endColor->red, + $color->endColor->green, + $color->endColor->blue, + 255 - $color->endColor->alpha + ); + + $fill = $shape->addFill( $gradient, SWFFILL_RADIAL_GRADIENT ); + + $fill->scaleTo( $this->modifyCoordinate( $color->width ) / 32768, $this->modifyCoordinate( $color->height ) / 32768 ); + $fill->moveTo( $this->modifyCoordinate( $color->center->x ), $this->modifyCoordinate( $color->center->y ) ); + + $shape->setLeftFill( $fill ); + break; + default: + $fill = $shape->addFill( $color->red, $color->green, $color->blue, 255 - $color->alpha ); + $shape->setLeftFill( $fill ); + break; + } + } + else + { + $shape->setLine( $this->modifyCoordinate( $thickness ), $color->red, $color->green, $color->blue, 255 - $color->alpha ); + } + } + + /** + * Modifies a coordinate value, as flash usally uses twips instead of + * pixels for a higher solution, as it only accepts integer values. + * + * @param float $pointValue + * @return float + */ + protected function modifyCoordinate( $pointValue ) + { + return $pointValue * 10; + } + + /** + * Demodifies a coordinate value, as flash usally uses twips instead of + * pixels for a higher solution, as it only accepts integer values. + * + * @param float $pointValue + * @return float + */ + protected function deModifyCoordinate( $pointValue ) + { + return $pointValue / 10; + } + + /** + * 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. ) + { + $movie = $this->getDocument(); + + if ( !$filled ) + { + // The middle of the border is on the outline of a polygon in ming, + // fix that: + try + { + $points = $this->reducePolygonSize( $points, $thickness / 2 ); + } + catch ( ezcGraphReducementFailedException $e ) + { + return false; + } + } + + $shape = new SWFShape(); + + $this->setShapeColor( $shape, $color, $thickness, $filled ); + + $lastPoint = end( $points ); + $shape->movePenTo( $this->modifyCoordinate( $lastPoint->x ), $this->modifyCoordinate( $lastPoint->y ) ); + + foreach ( $points as $point ) + { + $shape->drawLineTo( $this->modifyCoordinate( $point->x ), $this->modifyCoordinate( $point->y ) ); + } + + $object = $movie->add( $shape ); + $object->setName( $id = 'ezcGraphPolygon_' . $this->id++ ); + + return $id; + } + + /** + * 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. ) + { + $movie = $this->getDocument(); + + $shape = new SWFShape(); + + $this->setShapeColor( $shape, $color, $thickness, false ); + + $shape->movePenTo( $this->modifyCoordinate( $start->x ), $this->modifyCoordinate( $start->y ) ); + $shape->drawLineTo( $this->modifyCoordinate( $end->x ), $this->modifyCoordinate( $end->y ) ); + + $object = $movie->add( $shape ); + $object->setName( $id = 'ezcGraphLine_' . $this->id++ ); + + return $id; + } + + /** + * 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 ) + { + $t = new SWFText(); + $t->setFont( new SWFFont( $font->path ) ); + $t->setHeight( $size ); + + $boundings = new ezcGraphBoundings( 0, 0, $t->getWidth( $text ), $size ); + + return $boundings; + } + + /** + * 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 = $this->modifyCoordinate( $width - $padding * 2 ); + $height = $this->modifyCoordinate( $height - $padding * 2 ); + $position = new ezcGraphCoordinate( + $this->modifyCoordinate( $position->x + $padding ), + $this->modifyCoordinate( $position->y + $padding ) + ); + + // Try to get a font size for the text to fit into the box + $maxSize = $this->modifyCoordinate( min( $height, $this->options->font->maxFontSize ) ); + $minSize = $this->modifyCoordinate( $this->options->font->minFontSize ); + $result = false; + for ( $size = $maxSize; $size >= $minSize; ) + { + $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size ); + if ( is_array( $result ) ) + { + break; + } + $size = $this->deModifyCoordinate( $size ); + $size = $this->modifyCoordinate( 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->modifyCoordinate( $this->options->font->minFontSize ) ); + } + else + { + throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height ); + } + } + + + $this->options->font->minimalUsedFont = $this->deModifyCoordinate( $size ); + + $this->strings[] = array( + 'text' => $result, + 'id' => $id = 'ezcGraphTextBox_' . $this->id++, + 'position' => $position, + 'width' => $width, + 'height' => $height, + 'align' => $align, + 'font' => $this->options->font, + 'rotation' => $rotation, + ); + + return $id; + } + + /** + * Render text depending of font type and available font extensions + * + * @param string $id + * @param string $text + * @param string $chars + * @param int $type + * @param string $path + * @param ezcGraphColor $color + * @param ezcGraphCoordinate $position + * @param float $size + * @param float $rotation + * @return void + */ + protected function renderText( $id, $text, $chars, $type, $path, ezcGraphColor $color, ezcGraphCoordinate $position, $size, $rotation = null ) + { + $movie = $this->getDocument(); + + $tb = new SWFTextField( SWFTEXTFIELD_NOEDIT ); + $tb->setFont( new SWFFont( $path ) ); + $tb->setHeight( $size ); + $tb->setColor( $color->red, $color->green, $color->blue, 255 - $color->alpha ); + $tb->addString( $text ); + $tb->addChars( $chars ); + + $object = $movie->add( $tb ); + $object->rotate( + ( $rotation !== null ? -$rotation->getRotation() : 0 ) + ); + $object->moveTo( + $position->x + + ( $rotation === null ? 0 : $this->modifyCoordinate( $rotation->get( 0, 2 ) ) ), + $position->y - + $size * ( 1 + $this->options->lineSpacing ) + + ( $rotation === null ? 0 : $this->modifyCoordinate( $rotation->get( 1, 2 ) ) ) + ); + $object->setName( $id ); + } + + /** + * 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() + { + // Iterate over all strings to collect used chars per font + $chars = array(); + foreach ( $this->strings as $text ) + { + $completeString = ''; + foreach ( $text['text'] as $line ) + { + $completeString .= implode( ' ', $line ); + } + + // Collect chars for each font + if ( !isset( $chars[$text['font']->path] ) ) + { + $chars[$text['font']->path] = $completeString; + } + else + { + $chars[$text['font']->path] .= $completeString; + } + } + + foreach ( $this->strings as $text ) + { + $size = $this->modifyCoordinate( $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( + $this->deModifyCoordinate( $text['position']->x - $padding + $xOffset ), + $this->deModifyCoordinate( $text['position']->y - $padding + $yOffset ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $xOffset + $width ), + $this->deModifyCoordinate( $text['position']->y - $padding + $yOffset ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $xOffset + $width ), + $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $yOffset + $completeHeight ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x - $padding + $xOffset ), + $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $yOffset + $completeHeight ) + ), + ); + } + else + { + $borderPolygonArray = array( + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x - $padding ), + $this->deModifyCoordinate( $text['position']->y - $padding ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $text['width'] ), + $this->deModifyCoordinate( $text['position']->y - $padding ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $text['width'] ), + $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $text['height'] ) + ), + new ezcGraphCoordinate( + $this->deModifyCoordinate( $text['position']->x - $padding ), + $this->deModifyCoordinate( $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 + $completeString = ''; + foreach ( $text['text'] as $line ) + { + $string = implode( ' ', $line ); + $completeString .= $string; + $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; + } + + // Optionally draw text shadow + if ( $text['font']->textShadow === true ) + { + $this->renderText( + $text['id'], + $string, + $chars[$text['font']->path], + $text['font']->type, + $text['font']->path, + $text['font']->textShadowColor, + new ezcGraphCoordinate( + $position->x + $this->modifyCoordinate( $text['font']->textShadowOffset ), + $position->y + $this->modifyCoordinate( $text['font']->textShadowOffset ) + ), + $size, + $text['rotation'] + ); + } + + // Finally draw text + $this->renderText( + $text['id'], + $string, + $chars[$text['font']->path], + $text['font']->type, + $text['font']->path, + $text['font']->color, + $position, + $size, + $text['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 ) + { + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + $movie = $this->getDocument(); + + $shape = new SWFShape(); + $this->setShapeColor( $shape, $color, 1, $filled ); + + if ( !$filled ) + { + try + { + $reduced = $this->reduceEllipseSize( $center, $width, $height, $startAngle, $endAngle, .5 ); + } + catch ( ezcGraphReducementFailedException $e ) + { + return false; + } + + $startAngle = $reduced['startAngle']; + $endAngle = $reduced['endAngle']; + + $width -= 1; + $height -= 1; + } + + $shape->movePenTo( $this->modifyCoordinate( $center->x ), $this->modifyCoordinate( $center->y ) ); + + // @TODO: User SWFShape::curveTo + for( + $angle = $startAngle; + $angle <= $endAngle; + $angle = min( $angle + $this->options->circleResolution, $endAngle ) ) + { + $shape->drawLineTo( + $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ), + $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 ) + ); + + if ( $angle === $endAngle ) + { + break; + } + } + + $shape->drawLineTo( + $this->modifyCoordinate( $center->x ), + $this->modifyCoordinate( $center->y ) + ); + + $object = $movie->add( $shape ); + $object->setName( $id = 'ezcGraphCircleSector_' . $this->id++ ); + + return $id; + } + + /** + * Draws a circular arc consisting of several minor steps on the bounding + * lines. + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param mixed $size + * @param mixed $startAngle + * @param mixed $endAngle + * @param ezcGraphColor $color + * @param bool $filled + * @return string Element id + */ + protected function simulateCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled ) + { + $movie = $this->getDocument(); + $id = 'ezcGraphCircularArc_' . $this->id++; + + for ( + $tmpAngle = min( ceil ( $startAngle / 180 ) * 180, $endAngle ); + $tmpAngle <= $endAngle; + $tmpAngle = min( ceil ( $startAngle / 180 + 1 ) * 180, $endAngle ) ) + { + $shape = new SWFShape(); + $this->setShapeColor( $shape, $color, 1, $filled ); + + $shape->movePenTo( + $this->modifyCoordinate( $center->x + cos( deg2rad( $startAngle ) ) * $width / 2 ), + $this->modifyCoordinate( $center->y + sin( deg2rad( $startAngle ) ) * $height / 2 ) + ); + + // @TODO: Use SWFShape::curveTo + for( + $angle = $startAngle; + $angle <= $tmpAngle; + $angle = min( $angle + $this->options->circleResolution, $tmpAngle ) ) + { + $shape->drawLineTo( + $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ), + $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 + $size ) + ); + + if ( $angle === $tmpAngle ) + { + break; + } + } + + for( + $angle = $tmpAngle; + $angle >= $startAngle; + $angle = max( $angle - $this->options->circleResolution, $startAngle ) ) + { + $shape->drawLineTo( + $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ), + $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 ) + ); + + if ( $angle === $startAngle ) + { + break; + } + } + + $object = $movie->add( $shape ); + $object->setName( $id ); + + $startAngle = $tmpAngle; + if ( $tmpAngle === $endAngle ) + { + break; + } + } + + return $id; + } + + /** + * 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 ) + { + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + $id = $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $color, $filled ); + + if ( ( $this->options->shadeCircularArc !== false ) && + $filled ) + { + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $center->x - $width, + $center->y + ), + new ezcGraphCoordinate( + $center->x + $width, + $center->y + ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ), + ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc * 1.5 ) + ); + + $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $gradient, $filled ); + } + + return $id; + } + + /** + * 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 ) + { + $movie = $this->getDocument(); + + $shape = new SWFShape(); + $this->setShapeColor( $shape, $color, 1, $filled ); + + // Reduce size + if ( !$filled ) + { + $width -= 1; + $height -= 1; + } + + $shape->movePenTo( + $this->modifyCoordinate( $center->x + $width / 2 ), + $this->modifyCoordinate( $center->y ) + ); + + // @TODO: User SWFShape::curveTo + for ( $angle = $this->options->circleResolution; $angle < 360; $angle += $this->options->circleResolution ) + { + $shape->drawLineTo( + $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ), + $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 ) + ); + } + + $shape->drawLineTo( + $this->modifyCoordinate( $center->x + $width / 2 ), + $this->modifyCoordinate( $center->y ) + ); + + $object = $movie->add( $shape ); + $object->setName( $id = 'ezcGraphCircle_' . $this->id++ ); + + return $id; + } + + /** + * Draw an image + * + * The image will be inlined in the SVG document using data URL scheme. For + * this the mime type and base64 encoded file content will be merged to + * URL. + * + * @param mixed $file Image file + * @param ezcGraphCoordinate $position Top left position + * @param float $width Width of image in destination image + * @param float $height Height of image in destination image + * @return void + */ + public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ) + { + $movie = $this->getDocument(); + + $imageData = getimagesize( $file ); + if ( ( $imageData[2] !== IMAGETYPE_JPEG ) && ( $imageData[2] !== IMAGETYPE_PNG ) ) + { + throw new ezcGraphFlashBitmapTypeException( $imageData[2] ); + } + + // Try to create a new SWFBitmap object from provided file + $bitmap = new SWFBitmap( fopen( $file, 'rb' ) ); + + // Add the image to the movie + $object = $this->movie->add( $bitmap ); + + // Image size is calculated on the base of a tick size of 20, so + // that we need to transform this, to our tick size. + $factor = $this->modifyCoordinate( 1 ) / 20; + $object->scale( $factor, $factor ); + + // Scale by ratio of requested and original image size + $object->scale( + $width / $imageData[0], + $height / $imageData[1] + ); + + // Move object to the right position + $object->moveTo( + $this->modifyCoordinate( $position->x ), + $this->modifyCoordinate( $position->y ) + ); + + // Create, set and return unique ID + $object->setName( $id = 'ezcGraphImage_'. $this->id++ ); + return $id; + } + + /** + * Return mime type for current image format + * + * @return string + */ + public function getMimeType() + { + return 'application/x-shockwave-flash'; + } + + /** + * Finally save image + * + * @param string $file Destination filename + * @return void + */ + public function render( $file ) + { + $this->drawAllTexts(); + $movie = $this->getDocument(); + $movie->save( $file, $this->options->compression ); + } + + /** + * 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 SWFMovie + */ + public function getResource() + { + return $this->movie; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/driver/gd.php b/src/TUnit/external/ezc/Graph/driver/gd.php new file mode 100644 index 0000000..b1cca35 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/gd.php @@ -0,0 +1,1236 @@ + + * $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' ); + * + * + * @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; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/driver/svg.php b/src/TUnit/external/ezc/Graph/driver/svg.php new file mode 100644 index 0000000..110e311 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/svg.php @@ -0,0 +1,1230 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->background->color = '#FFFFFFFF'; + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->renderer = new ezcGraphRenderer3d(); + * $graph->renderer->options->pieChartShadowSize = 10; + * $graph->renderer->options->pieChartGleam = .5; + * $graph->renderer->options->dataBorder = false; + * $graph->renderer->options->pieChartHeight = 16; + * $graph->renderer->options->legendSymbolGleam = .5; + * + * // SVG driver options + * $graph->driver->options->templateDocument = dirname( __FILE__ ) . '/template.svg'; + * $graph->driver->options->graphOffset = new ezcGraphCoordinate( 25, 40 ); + * $graph->driver->options->insertIntoGroup = 'ezcGraph'; + * + * $graph->render( 400, 200, 'tutorial_driver_svg.svg' ); + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +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(); + + /** + * List of already created gradients + * + * @var array + */ + protected $drawnGradients = array(); + + /** + * Numeric unique element id + * + * @var int + */ + protected $elementID = 0; + + /** + * Font storage for SVG font glyphs and kernings. + * + * @var ezcGraphSvgFont + */ + protected $font = null; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'dom' ); + $this->options = new ezcGraphSvgDriverOptions( $options ); + $this->font = new ezcGraphSvgFont(); + } + + /** + * Creates the DOM object to insert SVG nodes in. + * + * If the DOM document does not exists it will be created or loaded + * according to the settings. + * + * @return void + */ + protected function createDocument() + { + if ( $this->dom === null ) + { + // Create encoding based dom document + if ( $this->options->encoding !== null ) + { + $this->dom = new DOMDocument( '1.0', $this->options->encoding ); + } + else + { + $this->dom = new DOMDocument( '1.0' ); + } + + if ( $this->options->templateDocument !== false ) + { + $this->dom->load( $this->options->templateDocument ); + + $this->defs = $this->dom->getElementsByTagName( 'defs' )->item( 0 ); + $svg = $this->dom->getElementsByTagName( 'svg' )->item( 0 ); + } + else + { + $svg = $this->dom->createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + $this->dom->appendChild( $svg ); + + $svg->setAttribute( 'width', $this->options->width ); + $svg->setAttribute( 'height', $this->options->height ); + $svg->setAttribute( 'version', '1.0' ); + $svg->setAttribute( 'id', $this->options->idPrefix ); + + $this->defs = $this->dom->createElement( 'defs' ); + $this->defs = $svg->appendChild( $this->defs ); + } + + if ( $this->options->insertIntoGroup !== false ) + { + // getElementById only works for Documents validated against a certain + // schema, so that the use of XPath should be faster in most cases. + $xpath = new DomXPath( $this->dom ); + $this->elements = $xpath->query( '//*[@id = \'' . $this->options->insertIntoGroup . '\']' )->item( 0 ); + if ( !$this->elements ) + { + throw new ezcGraphSvgDriverInvalidIdException( $this->options->insertIntoGroup ); + } + } + else + { + $this->elements = $this->dom->createElement( 'g' ); + $this->elements->setAttribute( 'id', $this->options->idPrefix . 'Chart' ); + $this->elements->setAttribute( 'color-rendering', $this->options->colorRendering ); + $this->elements->setAttribute( 'shape-rendering', $this->options->shapeRendering ); + $this->elements->setAttribute( 'text-rendering', $this->options->textRendering ); + $this->elements = $svg->appendChild( $this->elements ); + } + } + } + + /** + * Return gradient URL + * + * Creates the definitions needed for a gradient, if a proper gradient does + * not yet exists. In each case a URL referencing the correct gradient will + * be returned. + * + * @param ezcGraphColor $color Gradient + * @return string Gradient URL + */ + protected function getGradientUrl( ezcGraphColor $color ) + { + switch ( true ) + { + case ( $color instanceof ezcGraphLinearGradient ): + if ( !in_array( $color->__toString(), $this->drawnGradients, true ) ) + { + $gradient = $this->dom->createElement( 'linearGradient' ); + $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() ); + $this->defs->appendChild( $gradient ); + + // Start of linear gradient + $stop = $this->dom->createElement( 'stop' ); + $stop->setAttribute( 'offset', 0 ); + $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', + $color->startColor->red, + $color->startColor->green, + $color->startColor->blue, + 1 - ( $color->startColor->alpha / 255 ) + ) + ); + $gradient->appendChild( $stop ); + + // End of linear gradient + $stop = $this->dom->createElement( 'stop' ); + $stop->setAttribute( 'offset', 1 ); + $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', + $color->endColor->red, + $color->endColor->green, + $color->endColor->blue, + 1 - ( $color->endColor->alpha / 255 ) + ) + ); + $gradient->appendChild( $stop ); + + $gradient = $this->dom->createElement( 'linearGradient' ); + $gradient->setAttribute( 'id', $color->__toString() ); + $gradient->setAttribute( 'x1', sprintf( '%.4F', $color->startPoint->x ) ); + $gradient->setAttribute( 'y1', sprintf( '%.4F', $color->startPoint->y ) ); + $gradient->setAttribute( 'x2', sprintf( '%.4F', $color->endPoint->x ) ); + $gradient->setAttribute( 'y2', sprintf( '%.4F', $color->endPoint->y ) ); + $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' ); + $gradient->setAttributeNS( + 'http://www.w3.org/1999/xlink', + 'xlink:href', + '#Definition_' . $color->__toString() + ); + $this->defs->appendChild( $gradient ); + + $this->drawnGradients[] = $color->__toString(); + } + + return sprintf( 'url(#%s)', + $color->__toString() + ); + case ( $color instanceof ezcGraphRadialGradient ): + if ( !in_array( $color->__toString(), $this->drawnGradients, true ) ) + { + $gradient = $this->dom->createElement( 'linearGradient' ); + $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() ); + $this->defs->appendChild( $gradient ); + + // Start of linear gradient + $stop = $this->dom->createElement( 'stop' ); + $stop->setAttribute( 'offset', 0 ); + $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', + $color->startColor->red, + $color->startColor->green, + $color->startColor->blue, + 1 - ( $color->startColor->alpha / 255 ) + ) + ); + $gradient->appendChild( $stop ); + + // End of linear gradient + $stop = $this->dom->createElement( 'stop' ); + $stop->setAttribute( 'offset', 1 ); + $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', + $color->endColor->red, + $color->endColor->green, + $color->endColor->blue, + 1 - ( $color->endColor->alpha / 255 ) + ) + ); + $gradient->appendChild( $stop ); + + $gradient = $this->dom->createElement( 'radialGradient' ); + $gradient->setAttribute( 'id', $color->__toString() ); + $gradient->setAttribute( 'cx', sprintf( '%.4F', $color->center->x ) ); + $gradient->setAttribute( 'cy', sprintf( '%.4F', $color->center->y ) ); + $gradient->setAttribute( 'fx', sprintf( '%.4F', $color->center->x ) ); + $gradient->setAttribute( 'fy', sprintf( '%.4F', $color->center->y ) ); + $gradient->setAttribute( 'r', max( $color->height, $color->width ) ); + $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' ); + $gradient->setAttributeNS( + 'http://www.w3.org/1999/xlink', + 'xlink:href', + '#Definition_' . $color->__toString() + ); + $this->defs->appendChild( $gradient ); + + $this->drawnGradients[] = $color->__toString(); + } + + return sprintf( 'url(#%s)', + $color->__toString() + ); + default: + return false; + } + + } + + /** + * Get SVG style definition + * + * Returns a string with SVG style definitions created from color, + * fillstatus and line thickness. + * + * @param ezcGraphColor $color Color + * @param mixed $filled Filled + * @param float $thickness Line thickness. + * @return string Formatstring + */ + protected function getStyle( ezcGraphColor $color, $filled = true, $thickness = 1. ) + { + if ( $filled ) + { + if ( $url = $this->getGradientUrl( $color ) ) + { + return sprintf( 'fill: %s; stroke: none;', $url ); + } + else + { + return sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', + $color->red, + $color->green, + $color->blue, + 1 - ( $color->alpha / 255 ) + ); + } + } + else + { + if ( $url = $this->getGradientUrl( $color ) ) + { + return sprintf( 'fill: none; stroke: %s;', $url ); + } + else + { + return sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2F; stroke-linecap: %s; stroke-linejoin: %s;', + $color->red, + $color->green, + $color->blue, + $thickness, + 1 - ( $color->alpha / 255 ), + $this->options->strokeLineCap, + $this->options->strokeLineJoin + ); + } + } + } + + /** + * 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. ) + { + $this->createDocument(); + + if ( !$filled ) + { + // The middle of the border is on the outline of a polygon in SVG, + // fix that: + try + { + $points = $this->reducePolygonSize( $points, $thickness / 2 ); + } + catch ( ezcGraphReducementFailedException $e ) + { + return false; + } + } + + $lastPoint = end( $points ); + $pointString = sprintf( ' M %.4F,%.4F', + $lastPoint->x + $this->options->graphOffset->x, + $lastPoint->y + $this->options->graphOffset->y + ); + + foreach ( $points as $point ) + { + $pointString .= sprintf( ' L %.4F,%.4F', + $point->x + $this->options->graphOffset->x, + $point->y + $this->options->graphOffset->y + ); + } + $pointString .= ' z '; + + $path = $this->dom->createElement( 'path' ); + $path->setAttribute( 'd', $pointString ); + + $path->setAttribute( + 'style', + $this->getStyle( $color, $filled, $thickness ) + ); + $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Polygon_' . ++$this->elementID ) ); + $this->elements->appendChild( $path ); + + return $id; + } + + /** + * 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. ) + { + $this->createDocument(); + + $pointString = sprintf( ' M %.4F,%.4F L %.4F,%.4F', + $start->x + $this->options->graphOffset->x, + $start->y + $this->options->graphOffset->y, + $end->x + $this->options->graphOffset->x, + $end->y + $this->options->graphOffset->y + ); + + $path = $this->dom->createElement( 'path' ); + $path->setAttribute( 'd', $pointString ); + $path->setAttribute( + 'style', + $this->getStyle( $color, false, $thickness ) + ); + + $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Line_' . ++$this->elementID ) ); + $this->elements->appendChild( $path ); + + return $id; + } + + /** + * 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 ) + { + if ( $font->type === ezcGraph::SVG_FONT ) + { + return new ezcGraphBoundings( + 0, + 0, + $this->font->calculateStringWidth( $font->path, $text ) * $size, + $size + ); + } + else + { + // If we didn't get a SVG font, continue guessing the font width. + return new ezcGraphBoundings( + 0, + 0, + $this->getTextWidth( $text, $size ), + $size + ); + } + } + + /** + * 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; + $textPosition = new ezcGraphCoordinate( + $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; ) + { + $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size ); + if ( is_array( $result ) ) + { + break; + } + $size = ( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : floor( $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, + 'id' => $id = ( $this->options->idPrefix . 'TextBox_' . ++$this->elementID ), + 'position' => $textPosition, + 'width' => $width, + 'height' => $height, + 'align' => $align, + 'font' => $this->options->font, + 'rotation' => $rotation, + ); + + return $id; + } + + /** + * Guess text width for string + * + * The is no way to know the font or fontsize used by the SVG renderer to + * render the string. We assume some character width defined in the SVG + * driver options, tu guess the length of a string. We discern between + * numeric an non numeric strings, because we often use only numeric + * strings to display chart data and numbers tend to be a bit wider then + * characters. + * + * @param mixed $string + * @param mixed $size + * @access protected + * @return void + */ + protected function getTextWidth( $string, $size ) + { + switch ( strtolower( $this->options->encoding ) ) + { + case '': + case 'utf-8': + case 'utf-16': + $string = utf8_decode( $string ); + break; + } + + if ( is_numeric( $string ) ) + { + return $size * strlen( $string ) * $this->options->assumedNumericCharacterWidth; + } + else + { + return $size * strlen( $string ) * $this->options->assumedTextCharacterWidth; + } + } + + /** + * Encodes non-utf-8 strings + * + * Transforms non-utf-8 strings to their hex entities, because ext/DOM + * fails here with conversion errors. + * + * @param string $string + * @return string + */ + protected function encode( $string ) + { + $string = htmlspecialchars( $string ); + + switch ( strtolower( $this->options->encoding ) ) + { + case '': + case 'utf-8': + case 'utf-16': + return $string; + default: + // Manual escaping of non ANSII characters, because ext/DOM fails here + return preg_replace_callback( + '/[\\x80-\\xFF]/', + create_function( + '$char', + 'return sprintf( \'&#x%02x;\', ord( $char[0] ) );' + ), + $string + ); + } + } + + /** + * 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() + { + $elementsRoot = $this->elements; + + foreach ( $this->strings as $text ) + { + // Add all text elements into one group + $group = $this->dom->createElement( 'g' ); + $group->setAttribute( 'id', $text['id'] ); + + if ( $text['rotation'] !== null ) + { + $group->setAttribute( 'transform', sprintf( 'rotate( %.2F %.4F %.4F )', + $text['rotation']->getRotation(), + $text['rotation']->getCenter()->x, + $text['rotation']->getCenter()->y + ) ); + } + + $group = $elementsRoot->appendChild( $group ); + + $size = $text['font']->minimalUsedFont; + $font = $text['font']->name; + + $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 ); + if ( ( $strWidth = $this->getTextBoundings( $size, $text['font'], $string )->width ) > $width ) + { + $width = $strWidth; + } + } + + 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'] + ), + ); + } + + // Set elements root temporary to local text group to ensure + // background and border beeing elements of text group + $this->elements = $group; + if ( $text['font']->background !== false ) + { + $this->drawPolygon( + $borderPolygonArray, + $text['font']->background, + true + ); + } + else + { + // Always draw full tranparent background polygon as fallback, + // to be able to click on complete font space, not only on + // the text + $this->drawPolygon( + $borderPolygonArray, + ezcGraphColor::fromHex( '#FFFFFFFF' ), + true + ); + } + + if ( $text['font']->border !== false ) + { + $this->drawPolygon( + $borderPolygonArray, + $text['font']->border, + false, + $text['font']->borderWidth + ); + } + $this->elements = $elementsRoot; + + // Bottom line for SVG fonts is lifted a bit + $text['position']->y += $size * .85; + + // Render text with evaluated font size + foreach ( $text['text'] as $line ) + { + $string = implode( ' ', $line ); + + 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'] - $this->getTextBoundings( $size, $text['font'], $string )->width ), + $text['position']->y + $yOffset + ); + break; + case ( $text['align'] & ezcGraph::CENTER ): + $position = new ezcGraphCoordinate( + $text['position']->x + ( ( $text['width'] - $this->getTextBoundings( $size, $text['font'], $string )->width ) / 2 ), + $text['position']->y + $yOffset + ); + break; + } + + // Optionally draw text shadow + if ( $text['font']->textShadow === true ) + { + $textNode = $this->dom->createElement( 'text', $this->encode( $string ) ); + $textNode->setAttribute( 'id', $text['id'] . '_shadow' ); + $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x + $text['font']->textShadowOffset ) ); + $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) ); + $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y + $text['font']->textShadowOffset ) ); + $textNode->setAttribute( + 'style', + sprintf( + 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', + $size, + $text['font']->name, + $text['font']->textShadowColor->red, + $text['font']->textShadowColor->green, + $text['font']->textShadowColor->blue, + 1 - ( $text['font']->textShadowColor->alpha / 255 ) + ) + ); + $group->appendChild( $textNode ); + } + + // Finally draw text + $textNode = $this->dom->createElement( 'text', $this->encode( $string ) ); + $textNode->setAttribute( 'id', $text['id'] . '_text' ); + $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) ); + $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) ); + $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) ); + $textNode->setAttribute( + 'style', + sprintf( + 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', + $size, + $text['font']->name, + $text['font']->color->red, + $text['font']->color->green, + $text['font']->color->blue, + 1 - ( $text['font']->color->alpha / 255 ) + ) + ); + $group->appendChild( $textNode ); + + $text['position']->y += $size + $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 ) + { + $this->createDocument(); + + // Normalize angles + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + if ( ( $endAngle - $startAngle ) >= 360 ) + { + return $this->drawCircle( $center, $width, $height, $color, $filled ); + } + + // We need the radius + $width /= 2; + $height /= 2; + + // Apply offset to copy of center coordinate + $center = clone $center; + $center->x += $this->options->graphOffset->x; + $center->y += $this->options->graphOffset->y; + + if ( $filled ) + { + $Xstart = $center->x + $width * cos( -deg2rad( $startAngle ) ); + $Ystart = $center->y + $height * sin( deg2rad( $startAngle ) ); + $Xend = $center->x + $width * cos( ( -deg2rad( $endAngle ) ) ); + $Yend = $center->y + $height * sin( ( deg2rad( $endAngle ) ) ); + + $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 + ) + ); + + $arc->setAttribute( + 'style', + $this->getStyle( $color, $filled, 1 ) + ); + $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) ); + $this->elements->appendChild( $arc ); + return $id; + } + else + { + try + { + $reduced = $this->reduceEllipseSize( $center, $width * 2, $height * 2, $startAngle, $endAngle, .5 ); + } + catch ( ezcGraphReducementFailedException $e ) + { + return false; + } + + $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 + $reduced['center']->x, $reduced['center']->y, + // Startpoint + $reduced['start']->x, $reduced['start']->y, + // Radius + $width - .5, $height - .5, + // SVG-Stuff + ( $endAngle - $startAngle ) > 180, + // Endpoint + $reduced['end']->x, $reduced['end']->y + ) + ); + + $arc->setAttribute( + 'style', + $this->getStyle( $color, $filled, 1 ) + ); + + $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) ); + $this->elements->appendChild( $arc ); + + return $id; + } + } + + /** + * 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 ) + { + $this->createDocument(); + + // Normalize angles + if ( $startAngle > $endAngle ) + { + $tmp = $startAngle; + $startAngle = $endAngle; + $endAngle = $tmp; + } + + if ( ( $endAngle - $startAngle > 180 ) || + ( ( $startAngle % 180 != 0) && ( $endAngle % 180 != 0) && ( ( $startAngle % 360 > 180 ) XOR ( $endAngle % 360 > 180 ) ) ) ) + { + // Border crosses he 180 degrees border + $intersection = floor( $endAngle / 180 ) * 180; + while ( $intersection >= $endAngle ) + { + $intersection -= 180; + } + + $this->drawCircularArc( $center, $width, $height, $size, $startAngle, $intersection, $color, $filled ); + $this->drawCircularArc( $center, $width, $height, $size, $intersection, $endAngle, $color, $filled ); + return; + } + + // We need the radius + $width /= 2; + $height /= 2; + + $Xstart = $center->x + $this->options->graphOffset->x + $width * cos( -deg2rad( $startAngle ) ); + $Ystart = $center->y + $this->options->graphOffset->y + $height * sin( deg2rad( $startAngle ) ); + $Xend = $center->x + $this->options->graphOffset->x + $width * cos( ( -deg2rad( $endAngle ) ) ); + $Yend = $center->y + $this->options->graphOffset->y + $height * sin( ( deg2rad( $endAngle ) ) ); + + if ( $filled === true ) + { + $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 + ) + ); + } + else + { + $arc = $this->dom->createElement( 'path' ); + $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F', + // Startpoint + $Xstart, $Ystart, + // Radius + $width, $height, + // SVG-Stuff + ( $endAngle - $startAngle ) > 180, + // Endpoint + $Xend, $Yend + ) + ); + } + + $arc->setAttribute( + 'style', + $this->getStyle( $color, $filled ) + ); + + $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) ); + $this->elements->appendChild( $arc ); + + if ( ( $this->options->shadeCircularArc !== false ) && + $filled ) + { + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $center->x - $width, + $center->y + ), + new ezcGraphCoordinate( + $center->x + $width, + $center->y + ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ), + ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc ) + ); + + $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', + $this->getStyle( $gradient, $filled ) + ); + $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) ); + + $this->elements->appendChild( $arc ); + } + + return $id; + } + + /** + * 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 ) + { + $this->createDocument(); + + $ellipse = $this->dom->createElement( 'ellipse' ); + $ellipse->setAttribute( 'cx', sprintf( '%.4F', $center->x + $this->options->graphOffset->x ) ); + $ellipse->setAttribute( 'cy', sprintf( '%.4F', $center->y + $this->options->graphOffset->y ) ); + $ellipse->setAttribute( 'rx', sprintf( '%.4F', $width / 2 - ( $filled ? 0 : .5 ) ) ); + $ellipse->setAttribute( 'ry', sprintf( '%.4F', $height / 2 - ( $filled ? 0 : .5 ) ) ); + + $ellipse->setAttribute( + 'style', + $this->getStyle( $color, $filled, 1 ) + ); + + $ellipse->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Circle_' . ++$this->elementID ) ); + $this->elements->appendChild( $ellipse ); + + return $id; + } + + /** + * Draw an image + * + * The image will be inlined in the SVG document using data URL scheme. For + * this the mime type and base64 encoded file content will be merged to + * URL. + * + * @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->createDocument(); + + $data = getimagesize( $file ); + $image = $this->dom->createElement( 'image' ); + + $image->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) ); + $image->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) ); + $image->setAttribute( 'width', sprintf( '%.4Fpx', $width ) ); + $image->setAttribute( 'height', sprintf( '%.4Fpx', $height ) ); + $image->setAttributeNS( + 'http://www.w3.org/1999/xlink', + 'xlink:href', + sprintf( 'data:%s;base64,%s', + $data['mime'], + base64_encode( file_get_contents( $file ) ) + ) + ); + + $this->elements->appendChild( $image ); + $image->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Image_' . ++$this->elementID ) ); + + return $id; + } + + /** + * Return mime type for current image format + * + * @return string + */ + public function getMimeType() + { + return 'image/svg+xml'; + } + + /** + * 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() + { + $this->createDocument(); + $this->drawAllTexts(); + + header( 'Content-Type: ' . $this->getMimeType() ); + echo $this->dom->saveXML(); + } + + /** + * Finally save image + * + * @param string $file Destination filename + * @return void + */ + public function render( $file ) + { + $this->createDocument(); + $this->drawAllTexts(); + + // Embed used glyphs + $this->font->addFontToDocument( $this->dom ); + $this->dom->save( $file ); + } + + /** + * 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 DOMDocument + */ + public function getResource() + { + return $this->dom; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/driver/svg_font.php b/src/TUnit/external/ezc/Graph/driver/svg_font.php new file mode 100644 index 0000000..7858ccd --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/svg_font.php @@ -0,0 +1,298 @@ +`. + * + * Usage: + * + * $font = new ezcGraphSvgFont(); + * var_dump( + * $font->calculateStringWidth( '../tests/data/font.svg', 'Just a test string.' ), + * $font->calculateStringWidth( '../tests/data/font2.svg', 'Just a test string.' ) + * ); + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphSvgFont +{ + /** + * Units per EM + * + * @var float + */ + protected $unitsPerEm; + + /** + * Used glyphs + * + * @var array + */ + protected $usedGlyphs = array(); + + /** + * Cache for glyph size to save XPath lookups. + * + * @var array + */ + protected $glyphCache = array(); + + /** + * Used kernings + * + * @var array + */ + protected $usedKerns = array(); + + /** + * Path to font + * + * @var string + */ + protected $fonts = array(); + + /** + * Initialize SVG font + * + * Loads the SVG font $filename. This should be the path to the file + * generated by ttf2svg. + * + * Returns the (normlized) name of the initilized font. + * + * @param string $fontPath + * @return string + */ + protected function initializeFont( $fontPath ) + { + if ( isset( $this->fonts[$fontPath] ) ) + { + return $fontPath; + } + + // Check for existance of font file + if ( !is_file( $fontPath ) || !is_readable( $fontPath ) ) + { + throw new ezcBaseFileNotFoundException( $fontPath ); + } + + $this->fonts[$fontPath] = simplexml_load_file( $fontPath )->defs->font; + + // SimpleXML requires us to register a namespace for XPath to work + $this->fonts[$fontPath]->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' ); + + // Extract the number of units per Em + $this->unitsPerEm[$fontPath] = (int) $this->fonts[$fontPath]->{'font-face'}['units-per-em']; + + return $fontPath; + } + + /** + * Get name of font + * + * Get the name of the given font, by extracting its font family from the + * SVG font file. + * + * @param string $fontPath + * @return string + */ + public static function getFontName( $fontPath ) + { + $font = simplexml_load_file( $fontPath )->defs->font; + + // SimpleXML requires us to register a namespace for XPath to work + $font->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' ); + + // Extract the font family name + return (string) $font->{'font-face'}['font-family']; + } + + /** + * XPath has no standard means of escaping ' and ", with the only solution + * being to delimit your string with the opposite type of quote. ( And if + * your string contains both concat( ) it ). + * + * This method will correctly delimit $char with the appropriate quote type + * so that it can be used in an XPath expression. + * + * @param string $char + * @return string + */ + protected static function xpathEscape( $char ) + { + return "'" . str_replace( + array( '\'', '\\' ), + array( '\\\'', '\\\\' ), + $char ) . "'"; + } + + /** + * Returns the associated with $char. + * + * @param string $fontPath + * @param string $char + * @return float + */ + protected function getGlyph( $fontPath, $char ) + { + // Check if glyphwidth has already been calculated. + if ( isset( $this->glyphCache[$fontPath][$char] ) ) + { + return $this->glyphCache[$fontPath][$char]; + } + + $matches = $this->fonts[$fontPath]->xpath( + $query = "glyph[@unicode=" . self::xpathEscape( $char ) . "]" + ); + + if ( count( $matches ) === 0 ) + { + // Just ignore missing glyphs. The client will still render them + // using a default font. We try to estimate some width by using a + // common other character. + return $this->glyphCache[$fontPath][$char] = + ( $char === 'o' ? false : $this->getGlyph( $fontPath, 'o' ) ); + } + + $glyph = $matches[0]; + if ( !in_array( $glyph, $this->usedGlyphs ) ) + { + $this->usedGlyphs[$fontPath][] = $glyph; + } + + // There should only ever be one match + return $this->glyphCache[$fontPath][$char] = $glyph; + } + + /** + * Returns the amount of kerning to apply for glyphs $g1 and $g2. If no + * valid kerning pair can be found 0 is returned. + * + * @param string $fontPath + * @param SimpleXMLElement $g1 + * @param SimpleXMLElement $g2 + * @return int + */ + public function getKerning( $fontPath, SimpleXMLElement $glyph1, SimpleXMLElement $glyph2 ) + { + // Get the glyph names + $g1Name = self::xpathEscape( ( string ) $glyph1['glyph-name'] ); + $g2Name = self::xpathEscape( ( string ) $glyph2['glyph-name'] ); + + // Get the unicode character names + $g1Uni = self::xpathEscape( ( string ) $glyph1['unicode'] ); + $g2Uni = self::xpathEscape( ( string ) $glyph2['unicode'] ); + + // Search for kerning pairs + $pair = $this->fonts[$fontPath]->xpath( + "svg:hkern[( @g1=$g1Name and @g2=$g2Name ) + or + ( @u1=$g1Uni and @g2=$g2Uni )]" + ); + + // If we found anything return it + if ( count( $pair ) ) + { + if ( !in_array( $pair[0], $this->usedKerns ) ) + { + $this->usedKerns[$fontPath][] = $pair[0]; + } + + return ( int ) $pair[0]['k']; + } + else + { + return 0; + } + } + + /** + * Calculates the width of $string in the current font in Em's. + * + * @param string $fontPath + * @param string $string + * @return float + */ + public function calculateStringWidth( $fontPath, $string ) + { + // Ensure font is properly initilized + $fontPath = $this->initializeFont( $fontPath ); + + $strlen = strlen( $string ); + $prevCharInfo = null; + $length = 0; + // @TODO: Add UTF-8 support here - iterating over the bytes does not + // really help. + for ( $i = 0; $i < $strlen; ++$i ) + { + // Find the font information for the character + $charInfo = $this->getGlyph( $fontPath, $string[$i] ); + + // Handle missing glyphs + if ( $charInfo === false ) + { + $prevCharInfo = null; + $length += .5 * $this->unitsPerEm[$fontPath]; + continue; + } + + // Add the horizontal advance for the character to the length + $length += (float) $charInfo['horiz-adv-x']; + + // If we are not the first character, look for kerning pairs + if ( $prevCharInfo !== null ) + { + // Apply kerning (if any) + $length -= $this->getKerning( $fontPath, $prevCharInfo, $charInfo ); + } + + $prevCharInfo = clone $charInfo; + } + + // Divide by _unitsPerEm to get the length in Em + return (float) $length / $this->unitsPerEm[$fontPath]; + } + + /** + * Add font definitions to SVG document + * + * Add the SVG font definition paths for all used glyphs and kernings to + * the given SVG document. + * + * @param DOMDocument $document + * @return void + */ + public function addFontToDocument( DOMDocument $document ) + { + $defs = $document->getElementsByTagName( 'defs' )->item( 0 ); + + $fontNr = 0; + foreach ( $this->fonts as $path => $definition ) + { + // Just import complete font for now. + // @TODO: Only import used characters. + $font = dom_import_simplexml( $definition ); + $font = $document->importNode( $font, true ); + $font->setAttribute( 'id', 'Font' . ++$fontNr ); + + $defs->appendChild( $font ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/driver/verbose.php b/src/TUnit/external/ezc/Graph/driver/verbose.php new file mode 100644 index 0000000..3e67e73 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/driver/verbose.php @@ -0,0 +1,242 @@ +options = new ezcGraphSvgDriverOptions( $options ); + echo "\n"; + } + + /** + * Draws a single polygon + * + * @param array $points + * @param ezcGraphColor $color + * @param bool $filled + * @param float $thickness + * @return void + */ + public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ) + { + $pointString = ''; + foreach ( $points as $point ) + { + $pointString .= sprintf( "\t( %.2f, %.2f )\n", $point->x, $point->y ); + } + + printf( "% 4d: Draw %spolygon:\n%s", + $this->call++, + ( $filled ? 'filled ' : '' ), + $pointString + ); + } + + /** + * Draws a single line + * + * @param ezcGraphCoordinate $start + * @param ezcGraphCoordinate $end + * @param ezcGraphColor $color + * @param float $thickness + * @return void + */ + public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ) + { + printf( "% 4d: Draw line from ( %.2f, %.2f ) to ( %.2f, %.2f ) with thickness %d.\n", + $this->call++, + $start->x, + $start->y, + $end->x, + $end->y, + $thickness + ); + } + + /** + * 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 ) + { + return null; + } + + /** + * Wrties text in a box of desired size + * + * @param mixed $string + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @param int $align + * @param ezcGraphRotation $rotation + * @return void + */ + public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ) + { + printf( "% 4d: Draw text '%s' at ( %.2f, %.2f ) with dimensions ( %d, %d ) and alignement %d.\n", + $this->call++, + $string, + $position->x, + $position->y, + $width, + $height, + $align + ); + } + /** + * Draws a sector of cirlce + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param mixed $startAngle + * @param mixed $endAngle + * @param ezcGraphColor $color + * @param bool $filled + * @return void + */ + public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) + { + printf( "% 4d: Draw %scicle sector at ( %.2f, %.2f ) with dimensions ( %d, %d ) from %.2f to %.2f.\n", + $this->call++, + ( $filled ? 'filled ' : '' ), + $center->x, + $center->y, + $width, + $height, + $startAngle, + $endAngle + ); + } + + /** + * 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 ) + { + printf( "% 4d: Draw circular arc at ( %.2f, %.2f ) with dimensions ( %d, %d ) and size %.2f from %.2f to %.2f.\n", + $this->call++, + $center->x, + $center->y, + $width, + $height, + $size, + $startAngle, + $endAngle + ); + } + + /** + * Draws a circle + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param ezcGraphColor $color + * @param bool $filled + * + * @return void + */ + public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ) + { + printf( "% 4d: Draw %scircle at ( %.2f, %.2f ) with dimensions ( %d, %d ).\n", + $this->call++, + ( $filled ? 'filled ' : '' ), + $center->x, + $center->y, + $width, + $height + ); + } + + /** + * Draws a imagemap of desired size + * + * @param mixed $file + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @return void + */ + public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ) + { + printf( "% 4d: Draw image '%s' at ( %.2f, %.2f ) with dimensions ( %d, %d ).\n", + $this->call++, + $file, + $position->x, + $position->y, + $width, + $height + ); + } + + /** + * Return mime type for current image format + * + * @return string + */ + public function getMimeType() + { + return 'text/plain'; + } + + /** + * Finally save image + * + * @param mixed $file + * @return void + */ + public function render ( $file ) + { + printf( "Render image.\n" ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/element/axis.php b/src/TUnit/external/ezc/Graph/element/axis.php new file mode 100644 index 0000000..3792549 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/element/axis.php @@ -0,0 +1,535 @@ + + * $chart->xAxis->formatString = '%s %%'; + * + * + * For more complex formatting operations for the label you may assign a custom + * formatter function to the property $labelCallback. + * + * The orientation of labels and their position relatively to the axis ticks is + * calcualted and rendered by the ezcGraphAxisLabelRenderer classes. You can + * choose between different axis label renderer, or create you own, and assign + * an instance of one to the property $axisLabelRenderer. Currently the + * available axis label renderers are: + * + * - ezcGraphAxisBoxedLabelRenderer + * + * Renders grid and labels like commonly used in bar charts, with the label + * between two grid lines. + * + * - ezcGraphAxisCenteredLabelRenderer + * + * Centers the label right next to a tick. Commonly used for labeled axis. + * + * - ezcGraphAxisExactLabelRenderer + * + * Put the label next to each tick. Commonly used for numeric axis. + * + * - ezcGraphAxisNoLabelRenderer + * + * Renders no labels. + * + * - ezcGraphAxisRadarLabelRenderer + * + * Special label renderer for radar charts. + * + * - ezcGraphAxisRotatedLabelRenderer + * + * Accepts a rotation angle for the texts put at some axis, which might be + * useful for longer textual labels on the axis. + * + * The label renderer used by default is different depending on the axis type. + * + * @property float $nullPosition + * The position of the null value. + * @property float $axisSpace + * Percent of the chart space used to display axis labels and + * arrowheads instead of data values. + * @property float $outerAxisSpace + * Percent of the chart space used to display axis arrow at the outer + * side of the axis. If set to null, the axisSpace will be used here. + * @property ezcGraphColor $majorGrid + * Color of major majorGrid. + * @property ezcGraphColor $minorGrid + * Color of minor majorGrid. + * @property mixed $majorStep + * Labeled major steps displayed on the axis. @TODO: Should be moved + * to numeric axis. + * @property mixed $minorStep + * Non labeled minor steps on the axis. @TODO: Should be moved to + * numeric axis. + * @property string $formatString + * Formatstring to use for labeling of the axis. + * @property string $label + * Axis label + * @property int $labelSize + * Size of axis label + * @property int $labelMargin + * Distance between label an axis + * @property int $minArrowHeadSize + * Minimum Size used to draw arrow heads. + * @property int $maxArrowHeadSize + * Maximum Size used to draw arrow heads. + * @property ezcGraphAxisLabelRenderer $axisLabelRenderer + * AxisLabelRenderer used to render labels and grid on this axis. + * @property callback $labelCallback + * Callback function to format chart labels. + * Function will receive two parameters and should return a + * reformatted label. + * string function( label, step ) + * @property float $chartPosition + * Position of the axis in the chart. Only useful for additional + * axis. The basic chart axis will be automatically positioned. + * @property-read bool $initialized + * Property indicating if some values were associated with axis, or a + * scaling has been set manually. + * + * @version 1.4.3 + * @package Graph + */ +abstract class ezcGraphChartElementAxis extends ezcGraphChartElement +{ + /** + * Axis label renderer class + * + * @var ezcGraphAxisLabelRenderer + */ + protected $axisLabelRenderer; + + /** + * Optionally set inner boundings. May be null depending on the used chart + * implementation. + * + * @var ezcGraphBoundings + */ + protected $innerBoundings; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['nullPosition'] = false; + $this->properties['axisSpace'] = .1; + $this->properties['outerAxisSpace'] = null; + $this->properties['majorGrid'] = false; + $this->properties['minorGrid'] = false; + $this->properties['majorStep'] = null; + $this->properties['minorStep'] = null; + $this->properties['formatString'] = '%s'; + $this->properties['label'] = false; + $this->properties['labelSize'] = 14; + $this->properties['labelMargin'] = 2; + $this->properties['minArrowHeadSize'] = 4; + $this->properties['maxArrowHeadSize'] = 8; + $this->properties['labelCallback'] = null; + $this->properties['chartPosition'] = null; + $this->properties['initialized'] = false; + + parent::__construct( $options ); + + if ( !isset( $this->axisLabelRenderer ) ) + { + $this->axisLabelRenderer = new ezcGraphAxisExactLabelRenderer(); + } + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->border = $palette->axisColor; + $this->padding = $palette->padding; + $this->margin = $palette->margin; + $this->majorGrid = $palette->majorGridColor; + $this->minorGrid = $palette->minorGridColor; + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'nullPosition': + $this->properties['nullPosition'] = (float) $propertyValue; + break; + case 'axisSpace': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue >= 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float < 1' ); + } + + $this->properties['axisSpace'] = (float) $propertyValue; + break; + /* Do not yet allow to modify this value, this need further testing. + case 'outerAxisSpace': + if ( !is_null( $propertyValue ) && + ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue >= 1 ) ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'null, or 0 <= float < 1' ); + } + + $this->properties['outerAxisSpace'] = $propertyValue; + break; + */ + case 'majorGrid': + $this->properties['majorGrid'] = ezcGraphColor::create( $propertyValue ); + break; + case 'minorGrid': + $this->properties['minorGrid'] = ezcGraphColor::create( $propertyValue ); + break; + case 'majorStep': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['majorStep'] = (float) $propertyValue; + break; + case 'minorStep': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['minorStep'] = (float) $propertyValue; + break; + case 'formatString': + $this->properties['formatString'] = (string) $propertyValue; + break; + case 'label': + $this->properties['label'] = (string) $propertyValue; + break; + case 'labelSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 6 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 6' ); + } + + $this->properties['labelSize'] = (int) $propertyValue; + break; + case 'labelMargin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['labelMargin'] = (int) $propertyValue; + break; + case 'maxArrowHeadSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['maxArrowHeadSize'] = (int) $propertyValue; + break; + case 'minArrowHeadSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minArrowHeadSize'] = (int) $propertyValue; + break; + case 'axisLabelRenderer': + if ( $propertyValue instanceof ezcGraphAxisLabelRenderer ) + { + $this->axisLabelRenderer = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphAxisLabelRenderer' ); + } + break; + case 'labelCallback': + if ( is_callable( $propertyValue ) ) + { + $this->properties['labelCallback'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback function' ); + } + break; + case 'chartPosition': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['chartPosition'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * __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 'axisLabelRenderer': + return $this->axisLabelRenderer; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param float $value Value to determine position for + * @return float Position on chart + */ + abstract public function getCoordinate( $value ); + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + abstract public function getMinorStepCount(); + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + abstract public function getMajorStepCount(); + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + abstract public function getLabel( $step ); + + /** + * Return array of steps on this axis + * + * @return array( ezcGraphAxisStep ) + */ + public function getSteps() + { + $majorSteps = $this->getMajorStepCount(); + $minorStepsPerMajorStepCount = ( $this->getMinorStepCount() / $majorSteps ); + + $majorStepSize = 1 / $majorSteps; + $minorStepSize = ( $minorStepsPerMajorStepCount > 0 ? $majorStepSize / $minorStepsPerMajorStepCount : 0 ); + + $steps = array(); + for ( $major = 0; $major <= $majorSteps; ++$major ) + { + $majorStep = new ezcGraphAxisStep( + $majorStepSize * $major, + $majorStepSize, + $this->getLabel( $major ), + array(), + $this->isZeroStep( $major ), + ( $major === $majorSteps ) + ); + + if ( ( $minorStepsPerMajorStepCount > 0 ) && + ( $major < $majorSteps ) ) + { + // Do not add minor steps at major steps positions + for( $minor = 1; $minor < $minorStepsPerMajorStepCount; ++$minor ) + { + $majorStep->childs[] = new ezcGraphAxisStep( + ( $majorStepSize * $major ) + ( $minorStepSize * $minor ), + $minorStepSize + ); + } + } + + $steps[] = $majorStep; + } + + return $steps; + } + + /** + * Is zero step + * + * Returns true if the given step is the one on the initial axis position + * + * @param int $step Number of step + * @return bool Status If given step is initial axis position + */ + abstract public function isZeroStep( $step ); + + /** + * Add data for this axis + * + * @param array $values + * @return void + */ + abstract public function addData( array $values ); + + /** + * Calculate axis bounding values on base of the assigned values + * + * @abstract + * @access public + * @return void + */ + abstract public function calculateAxisBoundings(); + + /** + * Render the axis + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphBoundings $innerBoundings = null ) + { + $this->innerBoundings = $innerBoundings; + $startSpace = $this->axisSpace; + $endSpace = $this->outerAxisSpace === null ? $this->axisSpace : $this->outerAxisSpace; + + switch ( $this->position ) + { + case ezcGraph::TOP: + $start = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $startSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $startSpace ), + 0 + ); + $end = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $endSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $endSpace ), + $boundings->y1 - $boundings->y0 + ); + break; + case ezcGraph::BOTTOM: + $start = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $startSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $startSpace ), + $boundings->y1 - $boundings->y0 + ); + $end = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $endSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $endSpace ), + 0 + ); + break; + case ezcGraph::LEFT: + $start = new ezcGraphCoordinate( + 0, + ( $boundings->y1 - $boundings->y0 ) * $startSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $startSpace ) + ); + $end = new ezcGraphCoordinate( + $boundings->x1 - $boundings->x0, + ( $boundings->y1 - $boundings->y0 ) * $endSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $endSpace ) + ); + break; + case ezcGraph::RIGHT: + $start = new ezcGraphCoordinate( + $boundings->x1 - $boundings->x0, + ( $boundings->y1 - $boundings->y0 ) * $startSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $startSpace ) + ); + $end = new ezcGraphCoordinate( + 0, + ( $boundings->y1 - $boundings->y0 ) * $endSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $endSpace ) + ); + break; + } + + $renderer->drawAxis( + $boundings, + $start, + $end, + $this, + $this->axisLabelRenderer, + $innerBoundings + ); + + return $boundings; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/element/background.php b/src/TUnit/external/ezc/Graph/element/background.php new file mode 100644 index 0000000..6db97c2 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/element/background.php @@ -0,0 +1,221 @@ + + * $chart = new ezcGraphPieChart(); + * $chart->data['example'] = new ezcGraphArrayDataSet( array( + * 'Foo' => 23, + * 'Bar' => 42, + * ) ); + * + * $chart->background->image = 'background.png'; + * + * // Image would be repeated horizontal at the top of the background + * $chart->background->repeat = ezcGraph::HORIZONTAL; + * $chart->background->postion = ezcGraph::TOP; + * + * // Image would be placed once in the center + * $chart->background->repeat = ezcGraph::NO_REPEAT; // default; + * $chart->background->position = ezcGraph::CENTER | ezcGraph::MIDDLE; + * + * // Image would be repeated all over the chart, the position is irrelevant + * $chart->background->repeat = ezcGraph::HORIZONTAL | ezcGraph::VERTICAL; + * + * $graph->render( 400, 250, 'legend.svg' ); + * + * + * @property string $image + * Filename of the file to use for background + * @property int $repeat + * Defines how the background image gets repeated + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementBackground extends ezcGraphChartElement +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['image'] = false; + $this->properties['repeat'] = ezcGraph::NO_REPEAT; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'image': + // Check for existance of file + if ( !is_file( $propertyValue ) || !is_readable( $propertyValue ) ) + { + throw new ezcBaseFileNotFoundException( $propertyValue ); + } + + // Check for beeing an image file + $data = getImageSize( $propertyValue ); + if ( $data === false ) + { + throw new ezcGraphInvalidImageFileException( $propertyValue ); + } + + // SWF files are useless.. + if ( $data[2] === 4 ) + { + throw new ezcGraphInvalidImageFileException( 'We cant use SWF files like <' . $propertyValue . '>.' ); + } + + $this->properties['image'] = $propertyValue; + break; + case 'repeat': + if ( ( $propertyValue >= 0 ) && ( $propertyValue <= 3 ) ) + { + $this->properties['repeat'] = (int) $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 3' ); + } + break; + case 'position': + // Overwrite parent position setter, to be able to use + // combination of positions like + // ezcGraph::TOP | ezcGraph::CENTER + if ( is_int( $propertyValue ) ) + { + $this->properties['position'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'integer' ); + } + break; + case 'color': + // Use color as an alias to set background color for background + $this->__set( 'background', $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'color': + // Use color as an alias to set background color for background + return $this->properties['background']; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Set colors and border for this element + * + * Method is overwritten because we do not ant to apply the global padding + * and margin here. + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->border = $palette->chartBorderColor; + $this->borderWidth = $palette->chartBorderWidth; + $this->background = $palette->chartBackground; + $this->padding = 0; + $this->margin = 0; + } + + /** + * Render the background + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $boundings = $renderer->drawBox( + $boundings, + $this->background, + $this->border, + $this->borderWidth, + $this->margin, + $this->padding + ); + + if ( $this->image === false ) + { + return $boundings; + } + + $renderer->drawBackgroundImage( + $boundings, + $this->image, + $this->position, + $this->repeat + ); + + return $boundings; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/element/legend.php b/src/TUnit/external/ezc/Graph/element/legend.php new file mode 100644 index 0000000..c5a2112 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/element/legend.php @@ -0,0 +1,339 @@ + + * $chart->legend = false; + * + * + * The position of the legend in the chart can be influenced by the postion + * property, set to one of the position constants from the ezcGraph base class, + * like ezcGraph::BOTTOM, ezcGraph::LEFT, ezcGraph::RIGHT, ezcGraph::TOP. + * + * Depending on the position of the legend, either the $portraitSize (RIGHT, + * LEFT) or the $landscapeSize (TOP, BOTTOM) defines how much space will be + * aqquired for the legend. + * + * + * $graph = new ezcGraphPieChart(); + * $graph->data['example'] = new ezcGraphArrayDataSet( array( + * 'Foo' => 23, + * 'Bar' => 42, + * ) ); + * + * // Format the legend element + * $graph->legend->background = '#FFFFFF80'; + * + * // Place at the bottom of the chart, with a height of 5% of the remaining + * // chart space. + * $graph->legend->position = ezcGraph::BOTTOM; + * $graph->legend->landscapeSize = .05; + * + * $graph->render( 400, 250, 'legend.svg' ); + * + * + * @property float $portraitSize + * Size of a portrait style legend in percent of the size of the + * complete chart. + * @property float $landscapeSize + * Size of a landscape style legend in percent of the size of the + * complete chart. + * @property int $symbolSize + * Standard size of symbols and text in legends. + * @property float $minimumSymbolSize + * Scale symbol size up to to percent of complete legends size for + * very big legends. + * @property int $spacing + * Space between labels elements in pixel. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementLegend extends ezcGraphChartElement +{ + + /** + * Contains data which should be shown in the legend + * array( + * array( + * 'label' => (string) 'Label of data element', + * 'color' => (ezcGraphColor) $color, + * 'symbol' => (integer) ezcGraph::DIAMOND, + * ), + * ... + * ) + * + * @var array + */ + protected $labels; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['portraitSize'] = .2; + $this->properties['landscapeSize'] = .1; + $this->properties['symbolSize'] = 14; + $this->properties['padding'] = 1; + $this->properties['minimumSymbolSize'] = .05; + $this->properties['spacing'] = 2; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'padding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['padding'] = (int) $propertyValue; + break; + case 'symbolSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['symbolSize'] = (int) $propertyValue; + break; + case 'landscapeSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['landscapeSize'] = (float) $propertyValue; + break; + case 'portraitSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['portraitSize'] = (float) $propertyValue; + break; + case 'minimumSymbolSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['minimumSymbolSize'] = (float) $propertyValue; + break; + case 'spacing': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['spacing'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * __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 'labels': + return $this->labels; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Generate legend from several datasets with on entry per dataset + * + * @param ezcGraphChartDataContainer $datasets + * @return void + */ + public function generateFromDataSets( ezcGraphChartDataContainer $datasets ) + { + $this->labels = array(); + foreach ( $datasets as $dataset ) + { + $this->labels[] = array( + 'label' => $dataset->label->default, + 'url' => $dataset->url->default, + 'color' => $dataset->color->default, + 'symbol' => ( $dataset->symbol->default === null ? + ezcGraph::NO_SYMBOL : + $dataset->symbol->default ), + ); + } + } + + /** + * Generate legend from single dataset with on entry per data element + * + * @param ezcGraphDataSet $dataset + * @return void + */ + public function generateFromDataSet( ezcGraphDataSet $dataset ) + { + $this->labels = array(); + foreach ( $dataset as $label => $data ) + { + $this->labels[] = array( + 'label' => $label, + 'url' => $dataset->url[$label], + 'color' => $dataset->color[$label], + 'symbol' => ( $dataset->symbol[$label] === null ? + ezcGraph::NO_SYMBOL : + $dataset->symbol[$label] ), + ); + } + } + + /** + * Calculated boundings needed for the legend. + * + * Uses the position and the configured horizontal or vertical size of a + * legend to calculate the boundings for the legend. + * + * @param ezcGraphBoundings $boundings Avalable boundings + * @return ezcGraphBoundings Remaining boundings + */ + protected function calculateBoundings( ezcGraphBoundings $boundings ) + { + $this->properties['boundings'] = clone $boundings; + + switch ( $this->position ) + { + case ezcGraph::LEFT: + $size = ( $boundings->width ) * $this->portraitSize; + + $boundings->x0 += $size; + $this->boundings->x1 = $boundings->x0; + break; + case ezcGraph::RIGHT: + $size = ( $boundings->width ) * $this->portraitSize; + + $boundings->x1 -= $size; + $this->boundings->x0 = $boundings->x1; + break; + case ezcGraph::TOP: + $size = ( $boundings->height ) * $this->landscapeSize; + + $boundings->y0 += $size; + $this->boundings->y1 = $boundings->y0; + break; + case ezcGraph::BOTTOM: + $size = ( $boundings->height ) * $this->landscapeSize; + + $boundings->y1 -= $size; + $this->boundings->y0 = $boundings->y1; + break; + } + + return $boundings; + } + + /** + * Render a legend + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $boundings = $this->calculateBoundings( $boundings ); + + if ( $this->position === ezcGraph::LEFT || $this->position === ezcGraph::RIGHT ) + { + $type = ezcGraph::VERTICAL; + } + else + { + $type = ezcGraph::HORIZONTAL; + } + + // Render standard elements + $this->properties['boundings'] = $renderer->drawBox( + $this->properties['boundings'], + $this->properties['background'], + $this->properties['border'], + $this->properties['borderWidth'], + $this->properties['margin'], + $this->properties['padding'], + $this->properties['title'], + $this->getTitleSize( $this->properties['boundings'], $type ) + ); + + // Render legend + $renderer->drawLegend( + $this->boundings, + $this, + $type + ); + + return $boundings; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/element/text.php b/src/TUnit/external/ezc/Graph/element/text.php new file mode 100644 index 0000000..c67f7d7 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/element/text.php @@ -0,0 +1,150 @@ + + * $chart = new ezcGraphPieChart(); + * $chart->data['example'] = new ezcGraphArrayDataSet( array( + * 'Foo' => 23, + * 'Bar' => 42, + * ) ); + * + * $chart->title = 'Some pie chart'; + * + * // Use at maximum 5% of the chart height for the title. + * $chart->title->maxHeight = .05; + * + * $graph->render( 400, 250, 'title.svg' ); + * + * + * @property float $maxHeight + * Maximum percent of bounding used to display the text. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementText extends ezcGraphChartElement +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['maxHeight'] = .1; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'maxHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['maxHeight'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Render the text + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $height = (int) min( + round( $this->properties['maxHeight'] * ( $boundings->y1 - $boundings->y0 ) ), + $this->properties['font']->maxFontSize + $this->padding * 2 + $this->margin * 2 + ); + + switch ( $this->properties['position'] ) + { + case ezcGraph::TOP: + $textBoundings = new ezcGraphBoundings( + $boundings->x0, + $boundings->y0, + $boundings->x1, + $boundings->y0 + $height + ); + $boundings->y0 += $height + $this->properties['margin']; + break; + case ezcGraph::BOTTOM: + $textBoundings = new ezcGraphBoundings( + $boundings->x0, + $boundings->y1 - $height, + $boundings->x1, + $boundings->y1 + ); + $boundings->y1 -= $height + $this->properties['margin']; + break; + } + + $textBoundings = $renderer->drawBox( + $textBoundings, + $this->properties['background'], + $this->properties['border'], + $this->properties['borderWidth'], + $this->properties['margin'], + $this->properties['padding'] + ); + + $renderer->drawText( + $textBoundings, + $this->properties['title'], + ezcGraph::CENTER | ezcGraph::MIDDLE + ); + + return $boundings; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/exceptions/date_parsing.php b/src/TUnit/external/ezc/Graph/exceptions/date_parsing.php new file mode 100644 index 0000000..940ef0f --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/date_parsing.php @@ -0,0 +1,33 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/exception.php b/src/TUnit/external/ezc/Graph/exceptions/exception.php new file mode 100644 index 0000000..337148a --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/exception.php @@ -0,0 +1,20 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/flash_bitmap_type.php b/src/TUnit/external/ezc/Graph/exceptions/flash_bitmap_type.php new file mode 100644 index 0000000..628ff7d --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/flash_bitmap_type.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/font_rendering.php b/src/TUnit/external/ezc/Graph/exceptions/font_rendering.php new file mode 100644 index 0000000..c19ff00 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/font_rendering.php @@ -0,0 +1,40 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/font_type.php b/src/TUnit/external/ezc/Graph/exceptions/font_type.php new file mode 100644 index 0000000..c8ca941 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/font_type.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/incompatible_driver.php b/src/TUnit/external/ezc/Graph/exceptions/incompatible_driver.php new file mode 100644 index 0000000..efd585f --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/incompatible_driver.php @@ -0,0 +1,34 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_assignement.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_assignement.php new file mode 100644 index 0000000..fd3e509 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_assignement.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_data.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_data.php new file mode 100644 index 0000000..0c35755 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_data.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_data_source.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_data_source.php new file mode 100644 index 0000000..b2c6d48 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_data_source.php @@ -0,0 +1,33 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_dimensions.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_dimensions.php new file mode 100644 index 0000000..73414e4 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_dimensions.php @@ -0,0 +1,35 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_display_type.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_display_type.php new file mode 100644 index 0000000..568ed9a --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_display_type.php @@ -0,0 +1,46 @@ + 'Pie', + ezcGraph::LINE => 'Line', + ezcGraph::BAR => 'Bar', + ); + + if ( isset( $chartTypeNames[$type] ) ) + { + $chartTypeName = $chartTypeNames[$type]; + } + else + { + $chartTypeName = 'Unknown'; + } + + parent::__construct( "Invalid data set display type '$type' ('$chartTypeName') for current chart." ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_id.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_id.php new file mode 100644 index 0000000..f59ad52 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_id.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_image_file.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_image_file.php new file mode 100644 index 0000000..c1982fa --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_image_file.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_keys.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_keys.php new file mode 100644 index 0000000..f4630f8 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_keys.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/invalid_step_size.php b/src/TUnit/external/ezc/Graph/exceptions/invalid_step_size.php new file mode 100644 index 0000000..1c902b6 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/invalid_step_size.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/no_data.php b/src/TUnit/external/ezc/Graph/exceptions/no_data.php new file mode 100644 index 0000000..9cc74c4 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/no_data.php @@ -0,0 +1,30 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/no_such_data.php b/src/TUnit/external/ezc/Graph/exceptions/no_such_data.php new file mode 100644 index 0000000..fc70e80 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/no_such_data.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/no_such_dataset.php b/src/TUnit/external/ezc/Graph/exceptions/no_such_dataset.php new file mode 100644 index 0000000..9ecb5ac --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/no_such_dataset.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/no_such_element.php b/src/TUnit/external/ezc/Graph/exceptions/no_such_element.php new file mode 100644 index 0000000..34106e1 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/no_such_element.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/not_rendered.php b/src/TUnit/external/ezc/Graph/exceptions/not_rendered.php new file mode 100644 index 0000000..e3a8e4d --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/not_rendered.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/out_of_boundings.php b/src/TUnit/external/ezc/Graph/exceptions/out_of_boundings.php new file mode 100644 index 0000000..e18f16c --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/out_of_boundings.php @@ -0,0 +1,35 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/out_of_logarithmical_boundings.php b/src/TUnit/external/ezc/Graph/exceptions/out_of_logarithmical_boundings.php new file mode 100644 index 0000000..a9b46de --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/out_of_logarithmical_boundings.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/reducement_failed.php b/src/TUnit/external/ezc/Graph/exceptions/reducement_failed.php new file mode 100644 index 0000000..4049148 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/reducement_failed.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/too_many_datasets.php b/src/TUnit/external/ezc/Graph/exceptions/too_many_datasets.php new file mode 100644 index 0000000..9829511 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/too_many_datasets.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/unknown_color_definition.php b/src/TUnit/external/ezc/Graph/exceptions/unknown_color_definition.php new file mode 100644 index 0000000..46ad0b9 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/unknown_color_definition.php @@ -0,0 +1,32 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/unregular_steps.php b/src/TUnit/external/ezc/Graph/exceptions/unregular_steps.php new file mode 100644 index 0000000..27c7f4c --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/unregular_steps.php @@ -0,0 +1,31 @@ + diff --git a/src/TUnit/external/ezc/Graph/exceptions/unsupported_image_type.php b/src/TUnit/external/ezc/Graph/exceptions/unsupported_image_type.php new file mode 100644 index 0000000..cb734e9 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/exceptions/unsupported_image_type.php @@ -0,0 +1,61 @@ + 'GIF', + 2 => 'Jpeg', + 3 => 'PNG', + 4 => 'SWF', + 5 => 'PSD', + 6 => 'BMP', + 7 => 'TIFF (intel)', + 8 => 'TIFF (motorola)', + 9 => 'JPC', + 10 => 'JP2', + 11 => 'JPX', + 12 => 'JB2', + 13 => 'SWC', + 14 => 'IFF', + 15 => 'WBMP', + 16 => 'XBM', + + ); + + if ( isset( $typeName[$type] ) ) + { + $type = $typeName[$type]; + } + else + { + $type = 'Unknown'; + } + + parent::__construct( "Unsupported image format '{$type}'." ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/graph.php b/src/TUnit/external/ezc/Graph/graph.php new file mode 100644 index 0000000..4c191bf --- /dev/null +++ b/src/TUnit/external/ezc/Graph/graph.php @@ -0,0 +1,147 @@ + diff --git a/src/TUnit/external/ezc/Graph/interfaces/axis_label_renderer.php b/src/TUnit/external/ezc/Graph/interfaces/axis_label_renderer.php new file mode 100644 index 0000000..3bb2442 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/axis_label_renderer.php @@ -0,0 +1,557 @@ +properties['majorStepCount'] = false; + $this->properties['minorStepCount'] = false; + $this->properties['majorStepSize'] = 3; + $this->properties['minorStepSize'] = 1; + $this->properties['innerStep'] = true; + $this->properties['outerStep'] = false; + $this->properties['outerGrid'] = false; + $this->properties['showLabels'] = true; + $this->properties['labelPadding'] = 2; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'driver': + if ( $propertyValue instanceof ezcGraphDriver ) + { + $this->properties['driver'] = $propertyValue; + } + else + { + throw new ezcGraphInvalidDriverException( $propertyValue ); + } + break; + case 'majorStepCount': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['majorStepCount'] = (int) $propertyValue; + break; + case 'minorStepCount': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minorStepCount'] = (int) $propertyValue; + break; + case 'majorStepSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['majorStepSize'] = (int) $propertyValue; + break; + case 'minorStepSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minorStepSize'] = (int) $propertyValue; + break; + case 'innerStep': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['innerStep'] = (bool) $propertyValue; + break; + case 'outerStep': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['outerStep'] = (bool) $propertyValue; + break; + case 'outerGrid': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['outerGrid'] = (bool) $propertyValue; + break; + case 'showLabels': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['showLabels'] = (bool) $propertyValue; + break; + case 'labelPadding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['labelPadding'] = (int) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Checks for the cutting point of two lines. + * + * The lines are given by a start position and the direction of the line, + * both as instances of {@link ezcGraphCoordinate}. If no cutting point + * could be calculated, because the lines are parallel the function will + * return false. Otherwise the factor returned can be used to calculate the + * cutting point using the following equatation: + * point = $aStart + $factor * $aDir; + * + * We return the factor instead of the resulting point because it can be + * easily determined from the factor if the cutting point is in "behind" + * the line starting point, or if the distance to the cutting point is + * bigger then the direction vector is long ( $factor > 1 ). + * + * @param ezcGraphCoordinate $aStart + * @param ezcGraphCoordinate $aDir + * @param ezcGraphCoordinate $bStart + * @param ezcGraphCoordinate $bDir + * @return mixed + */ + public function determineLineCuttingPoint( ezcGraphCoordinate $aStart, ezcGraphCoordinate $aDir, ezcGraphCoordinate $bStart, ezcGraphCoordinate $bDir ) + { + // Check if lines are parallel + if ( ( ( abs( $aDir->x ) < .000001 ) && ( abs( $bDir->x ) < .000001 ) ) || + ( ( abs( $aDir->y ) < .000001 ) && ( abs( $bDir->y ) < .000001 ) ) || + ( ( abs( $aDir->x * $bDir->x * $aDir->y * $bDir->y ) > .000001 ) && + ( abs( ( $aDir->x / $aDir->y ) - ( $bDir->x / $bDir->y ) ) < .000001 ) + ) + ) + { + return false; + } + + // Use ? : to prevent division by zero + $denominator = + ( abs( $aDir->y ) > .000001 ? $bDir->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bDir->x / $aDir->x : .0 ); + + // Solve equatation + if ( abs( $denominator ) < .000001 ) + { + return - ( + ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) - + ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) + + ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 ) + ); + } + else + { + return - ( + ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) - + ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) + + ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 ) + ) / $denominator; + } + } + + /** + * Draw single step on a axis + * + * Draws a step on a axis at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the step with + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param int $axisPosition Position of axis + * @param int $size Step size + * @param ezcGraphColor $color Color of axis + * @return void + */ + public function drawStep( ezcGraphRenderer $renderer, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, $axisPosition, $size, ezcGraphColor $color ) + { + if ( ! ( $this->innerStep || $this->outerStep ) ) + { + return false; + } + + $drawStep = false; + if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::TOP ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::RIGHT ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::LEFT ) && $this->innerStep ) ) + { + // Turn direction vector to left by 90 degrees and multiply + // with major step size + $stepStart = new ezcGraphCoordinate( + $position->x + $direction->y * $size, + $position->y - $direction->x * $size + ); + $drawStep = true; + } + else + { + $stepStart = $position; + } + + if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::TOP ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::RIGHT ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::LEFT ) && $this->outerStep ) ) + { + // Turn direction vector to right by 90 degrees and multiply + // with major step size + $stepEnd = new ezcGraphCoordinate( + $position->x - $direction->y * $size, + $position->y + $direction->x * $size + ); + $drawStep = true; + } + else + { + $stepEnd = $position; + } + + if ( $drawStep ) + { + $renderer->drawStepLine( + $stepStart, + $stepEnd, + $color + ); + } + } + + /** + * Draw non-rectangular grid lines grid + * + * Draws a grid line at the current position, for non-rectangular axis. + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @return void + */ + protected function drawNonRectangularGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color ) + { + // Direction of grid line is direction of axis turned right by 90 + // degrees + $gridDirection = new ezcGraphCoordinate( + $direction->y, + - $direction->x + ); + + $cuttingPoints = array(); + foreach ( array( // Bounding lines + array( + 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + 'dir' => new ezcGraphCoordinate( 0, $boundings->y1 - $boundings->y0 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + 'dir' => new ezcGraphCoordinate( $boundings->x1 - $boundings->x0, 0 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + 'dir' => new ezcGraphCoordinate( 0, $boundings->y0 - $boundings->y1 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + 'dir' => new ezcGraphCoordinate( $boundings->x0 - $boundings->x1, 0 ) + ), + ) as $boundingLine ) + { + // Test for cutting points with bounding lines, where cutting + // position is between 0 and 1, which means, that the line is hit + // on the bounding box rectangle. Use these points as a start and + // ending point for the grid lines. There should *always* be + // exactly two points returned. + $cuttingPosition = $this->determineLineCuttingPoint( + $boundingLine['start'], + $boundingLine['dir'], + $position, + $gridDirection + ); + + if ( $cuttingPosition === false ) + { + continue; + } + + $cuttingPosition = abs( $cuttingPosition ); + + if ( ( $cuttingPosition >= 0 ) && + ( $cuttingPosition <= 1 ) ) + { + $cuttingPoints[] = new ezcGraphCoordinate( + $boundingLine['start']->x + $cuttingPosition * $boundingLine['dir']->x, + $boundingLine['start']->y + $cuttingPosition * $boundingLine['dir']->y + ); + } + } + + if ( count( $cuttingPoints ) < 2 ) + { + // This should not happpen + return false; + } + + // Finally draw grid line + $renderer->drawGridLine( + $cuttingPoints[0], + $cuttingPoints[1], + $color + ); + } + + /** + * Draw rectangular grid + * + * Draws a grid line at the current position for rectangular directed axis. + * + * Method special for rectangularly directed axis to minimize the floating + * point calculation inaccuracies. Those are not necessary for rectangles, + * while for non-rectangular directed axis. + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @return void + */ + protected function drawRectangularGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color ) + { + if ( abs( $direction->x ) < .00001 ) + { + $renderer->drawGridLine( + new ezcGraphCoordinate( + $boundings->x0, + $position->y + ), + new ezcGraphCoordinate( + $boundings->x1, + $position->y + ), + $color + ); + } + else + { + $renderer->drawGridLine( + new ezcGraphCoordinate( + $position->x, + $boundings->y0 + ), + new ezcGraphCoordinate( + $position->x, + $boundings->y1 + ), + $color + ); + } + } + + /** + * Draw grid + * + * Draws a grid line at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @return void + */ + protected function drawGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color ) + { + // Check if the axis direction is rectangular + if ( ( abs( $direction->x ) < .00001 ) || + ( abs( $direction->y ) < .00001 ) ) + { + return $this->drawRectangularGrid( $renderer, $boundings, $position, $direction, $color ); + } + else + { + return $this->drawNonRectangularGrid( $renderer, $boundings, $position, $direction, $color ); + } + } + + /** + * Modify chart boundings + * + * Optionally modify boundings of chart data + * + * @param ezcGraphBoundings $boundings Current boundings of chart + * @param ezcGraphCoordinate $direction Direction of the current axis + * @return ezcGraphBoundings Modified boundings + */ + public function modifyChartBoundings( ezcGraphBoundings $boundings, ezcGraphCoordinate $direction ) + { + return $boundings; + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + return $coordinate; + } + + /** + * Get axis space values + * + * Get axis space values, depending on passed parameters. If + * $innerBoundings is given it will be used to caclulat the axis spaces + * available for label rendering. If not given the legacy method will be + * used, which uses the xAxisSpace and yAxisSpace values calcualted by the + * renderer. + * + * Returns an array( $xSpace, $ySpace ), containing the irespective size in + * pixels. Additionally calculates the grid boundings passed by reference. + * + * @param ezcGraphRenderer $renderer + * @param ezcGraphBoundings $boundings + * @param mixed $innerBoundings + * @return array + */ + protected function getAxisSpace( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphChartElementAxis $axis, $innerBoundings, &$gridBoundings ) + { + if ( $innerBoundings !== null ) + { + $gridBoundings = clone $innerBoundings; + $xSpace = abs( $axis->position === ezcGraph::LEFT ? $innerBoundings->x0 - $boundings->x0 : $boundings->x1 - $innerBoundings->x1 ); + $ySpace = abs( $axis->position === ezcGraph::TOP ? $innerBoundings->y0 - $boundings->y0 : $boundings->y1 - $innerBoundings->y1 ); + } + else + { + $gridBoundings = new ezcGraphBoundings( + $boundings->x0 + ( $xSpace = abs( $renderer->xAxisSpace ) ), + $boundings->y0 + ( $ySpace = abs( $renderer->yAxisSpace ) ), + $boundings->x1 - $xSpace, + $boundings->y1 - $ySpace + ); + } + + if ( $this->outerGrid ) + { + $gridBoundings = $boundings; + } + + return array( $xSpace, $ySpace ); + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + abstract public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis + ); +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/chart.php b/src/TUnit/external/ezc/Graph/interfaces/chart.php new file mode 100644 index 0000000..9c30eb7 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/chart.php @@ -0,0 +1,296 @@ +palette = new ezcGraphPaletteTango(); + $this->data = new ezcGraphChartDataContainer( $this ); + + // Add standard elements + $this->addElement( 'background', new ezcGraphChartElementBackground() ); + $this->elements['background']->position = ezcGraph::CENTER | ezcGraph::MIDDLE; + + $this->addElement( 'title', new ezcGraphChartElementText() ); + $this->elements['title']->position = ezcGraph::TOP; + $this->renderElement['title'] = false; + + $this->addElement( 'subtitle', new ezcGraphChartElementText() ); + $this->elements['subtitle']->position = ezcGraph::TOP; + $this->renderElement['subtitle'] = false; + + $this->addElement( 'legend', new ezcGraphChartElementLegend() ); + $this->elements['legend']->position = ezcGraph::LEFT; + + // Define standard renderer and driver + $this->properties['driver'] = new ezcGraphSvgDriver(); + $this->properties['renderer'] = new ezcGraphRenderer2d(); + $this->properties['renderer']->setDriver( $this->driver ); + + // Initialize other properties + $this->properties['renderedFile'] = null; + } + + /** + * Add element to chart + * + * Add a chart element to the chart and perform the required configuration + * tasks for the chart element. + * + * @param string $name Element name + * @param ezcGraphChartElement $element Chart element + * @return void + */ + protected function addElement( $name, ezcGraphChartElement $element ) + { + $this->elements[$name] = $element; + $this->elements[$name]->font = $this->options->font; + $this->elements[$name]->setFromPalette( $this->palette ); + + // Render element by default + $this->renderElement[$name] = true; + } + + /** + * Options write access + * + * @throws ezcBasePropertyNotFoundException + * If Option could not be found + * @throws ezcBaseValueException + * If value is out of range + * @param mixed $propertyName Option name + * @param mixed $propertyValue Option value; + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) { + case 'title': + case 'subtitle': + $this->elements[$propertyName]->title = $propertyValue; + $this->renderElement[$propertyName] = true; + break; + case 'background': + $this->elements[$propertyName]->color = $propertyValue; + break; + case 'legend': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'boolean' ); + } + + $this->renderElement['legend'] = (bool) $propertyValue; + break; + case 'renderer': + if ( $propertyValue instanceof ezcGraphRenderer ) + { + $this->properties['renderer'] = $propertyValue; + $this->properties['renderer']->setDriver( $this->driver ); + return $this->properties['renderer']; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphRenderer' ); + } + break; + case 'driver': + if ( $propertyValue instanceof ezcGraphDriver ) + { + $this->properties['driver'] = $propertyValue; + $this->properties['renderer']->setDriver( $this->driver ); + return $this->properties['driver']; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphDriver' ); + } + break; + case 'palette': + if ( $propertyValue instanceof ezcGraphPalette ) + { + $this->properties['palette'] = $propertyValue; + $this->setFromPalette( $this->palette ); + } + else + { + throw new ezcBaseValueException( "palette", $propertyValue, "instanceof ezcGraphPalette" ); + } + + break; + case 'renderedFile': + $this->properties['renderedFile'] = (string) $propertyValue; + break; + case 'options': + if ( $propertyValue instanceof ezcGraphChartOptions ) + { + $this->options = $propertyValue; + } + else + { + throw new ezcBaseValueException( "options", $propertyValue, "instanceof ezcGraphOptions" ); + } + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->options->font->name = $palette->fontName; + $this->options->font->color = $palette->fontColor; + + foreach ( $this->elements as $element ) + { + $element->setFromPalette( $palette ); + } + } + + /** + * __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 ) + { + if ( array_key_exists( $propertyName, $this->properties ) ) + { + return $this->properties[$propertyName]; + } + + if ( isset( $this->elements[$propertyName] ) ) + { + return $this->elements[$propertyName]; + } + + if ( ( $propertyName === 'options' ) || + ( $propertyName === 'data' ) ) + { + return $this->$propertyName; + } + else + { + throw new ezcGraphNoSuchElementException( $propertyName ); + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + abstract public function getDefaultDisplayType(); + + /** + * Return filename of rendered file, and false if no file was yet rendered. + * + * @return mixed + */ + public function getRenderedFile() + { + return ( $this->renderedFile !== null ? $this->renderedFile : false ); + } + + /** + * Renders this chart + * + * Creates basic visual chart elements from the chart to be processed by + * the renderer. + * + * @param int $width + * @param int $height + * @param string $file + * @return void + */ + abstract public function render( $width, $height, $file = null ); + + /** + * Renders this chart to direct output + * + * Does the same as ezcGraphChart::render(), but renders directly to + * output and not into a file. + * + * @param int $width + * @param int $height + * @return void + */ + abstract public function renderToOutput( $width, $height ); +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/dataset_property.php b/src/TUnit/external/ezc/Graph/interfaces/dataset_property.php new file mode 100644 index 0000000..72b6035 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/dataset_property.php @@ -0,0 +1,209 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->data['example'] = new ezcGraphArrayDataSet( array( + * 'Foo' => 23, + * 'Bar' => 42, + * ) ); + * + * // Set color for all data points in this data set + * $graph->data['example']->color = '#a40000'; + * + * // Set different color for one special datapoint + * $graph->data['example']->color['Foo'] = '#2e3436'; + * + * $graph->render( 400, 200, 'test.svg' ); + * + * + * @version 1.4.3 + * @package Graph + */ +abstract class ezcGraphDataSetProperty implements ArrayAccess +{ + /** + * Default value for this property + * + * @var mixed + */ + protected $defaultValue; + + /** + * Contains specified values for single dataset elements + * + * @var array + */ + protected $dataValue; + + /** + * Contains a reference to the dataset to check for availability of data + * keys + * + * @var ezcGraphDataSet + */ + protected $dataset; + + /** + * Abstract method to contain the check for validity of the value + * + * @param mixed $value + * @return void + */ + abstract protected function checkValue( &$value ); + + /** + * Constructor + * + * @param ezcGraphDataSet $dataset + * @ignore + * @return void + */ + public function __construct( ezcGraphDataSet $dataset ) + { + $this->dataset = $dataset; + } + + /** + * Set the default value for this property + * + * @param string $name Property name + * @param mixed $value Property value + * @return void + */ + public function __set( $name, $value ) + { + if ( $name === 'default' && + $this->checkValue( $value ) ) + { + $this->defaultValue = $value; + } + } + + /** + * Get the default value for this property + * + * @param string $name Property name + * @return mixed + */ + public function __get( $name ) + { + if ( $name === 'default' ) + { + return $this->defaultValue; + } + } + + /** + * Returns if an option exists. + * Allows isset() using ArrayAccess. + * + * @param string $key The name of the option to get. + * @return bool Wether the option exists. + */ + final public function offsetExists( $key ) + { + return isset( $this->dataset[$key] ); + } + + /** + * Returns an option value. + * Get an option value by ArrayAccess. + * + * @param string $key The name of the option to get. + * @return mixed The option value. + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + */ + final public function offsetGet( $key ) + { + if ( isset( $this->dataValue[$key] ) ) + { + return $this->dataValue[$key]; + } + elseif ( isset( $this->dataset[$key] ) ) + { + return $this->defaultValue; + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } + + /** + * Set an option. + * Sets an option using ArrayAccess. + * + * @param string $key The option to set. + * @param mixed $value The value for the option. + * @return void + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @throws ezcBaseValueException + * If a the value for a property is out of range. + */ + public function offsetSet( $key, $value ) + { + if ( isset( $this->dataset[$key] ) && + $this->checkValue( $value ) ) + { + $this->dataValue[$key] = $value; + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } + + /** + * Unset an option. + * Unsets an option using ArrayAccess. + * + * @param string $key The options to unset. + * @return void + * + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @throws ezcBaseValueException + * If a the value for a property is out of range. + */ + final public function offsetUnset( $key ) + { + if ( isset( $this->dataset[$key] ) ) + { + unset( $this->dataValue[$key] ); + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/driver.php b/src/TUnit/external/ezc/Graph/interfaces/driver.php new file mode 100644 index 0000000..b4ddc9f --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/driver.php @@ -0,0 +1,740 @@ +options = $propertyValue; + } + else + { + throw new ezcBaseValueException( "options", $propertyValue, "instanceof ezcGraphOptions" ); + } + break; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * __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: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Reduces the size of a polygon + * + * The method takes a polygon defined by a list of points and reduces its + * size by moving all lines to the middle by the given $size value. + * + * The detection of the inner side of the polygon depends on the angle at + * each edge point. This method will always work for 3 edged polygones, + * because the smaller angle will always be on the inner side. For + * polygons with more then 3 edges this method may fail. For ezcGraph this + * is a valid simplification, because we do not have any polygones which + * have an inner angle >= 180 degrees. + * + * @param array(ezcGraphCoordinate) $points + * @param float $size + * @throws ezcGraphReducementFailedException + * @return array( ezcGraphCoordinate ) + */ + protected function reducePolygonSize( array $points, $size ) + { + $pointCount = count( $points ); + + // Build normalized vectors between polygon edge points + $vectors = array(); + $vectorLength = array(); + for ( $i = 0; $i < $pointCount; ++$i ) + { + $nextPoint = ( $i + 1 ) % $pointCount; + $vectors[$i] = ezcGraphVector::fromCoordinate( $points[$nextPoint] ) + ->sub( $points[$i] ); + + // Throw exception if polygon is too small to reduce + $vectorLength[$i] = $vectors[$i]->length(); + if ( $vectorLength[$i] < $size ) + { + throw new ezcGraphReducementFailedException(); + } + $vectors[$i]->unify(); + + // Remove point from list if it the same as the next point + if ( ( $vectors[$i]->x == $vectors[$i]->y ) && ( $vectors[$i]->x == 0 ) ) + { + $pointCount--; + if ( $i === 0 ) + { + $points = array_slice( $points, $i + 1 ); + } + else + { + $points = array_merge( + array_slice( $points, 0, $i ), + array_slice( $points, $i + 1 ) + ); + } + $i--; + } + } + + // Remove vectors and appendant point, if local angle equals zero + // dergrees. + for ( $i = 0; $i < $pointCount; ++$i ) + { + $nextPoint = ( $i + 1 ) % $pointCount; + + if ( ( abs( $vectors[$i]->x - $vectors[$nextPoint]->x ) < .0001 ) && + ( abs( $vectors[$i]->y - $vectors[$nextPoint]->y ) < .0001 ) ) + { + $pointCount--; + + $points = array_merge( + array_slice( $points, 0, $i + 1 ), + array_slice( $points, $i + 2 ) + ); + $vectors = array_merge( + array_slice( $vectors, 0, $i + 1 ), + array_slice( $vectors, $i + 2 ) + ); + $i--; + } + } + + // No reducements for lines + if ( $pointCount <= 2 ) + { + return $points; + } + + // Determine one of the angles - we need to know where the smaller + // angle is, to determine if the inner side of the polygon is on + // the left or right hand. + // + // This is a valid simplification for ezcGraph(, for now). + // + // The sign of the scalar products results indicates on which site + // the smaller angle is, when comparing the orthogonale vector of + // one of the vectors with the other. Why? .. use pen and paper .. + // + // It is sufficant to do this once before iterating over the points, + // because the inner side of the polygon is on the same side of the + // point for each point. + $last = 0; + $next = 1; + + $sign = ( + -$vectors[$last]->y * $vectors[$next]->x + + $vectors[$last]->x * $vectors[$next]->y + ) < 0 ? 1 : -1; + + // Move points to center + $newPoints = array(); + for ( $i = 0; $i < $pointCount; ++$i ) + { + $last = $i; + $next = ( $i + 1 ) % $pointCount; + + // Orthogonal vector with direction based on the side of the inner + // angle + $v = clone $vectors[$next]; + if ( $sign > 0 ) + { + $v->rotateCounterClockwise()->scalar( $size ); + } + else + { + $v->rotateClockwise()->scalar( $size ); + } + + // get last vector not pointing in reverse direction + $lastVector = clone $vectors[$last]; + $lastVector->scalar( -1 ); + + // Calculate new point: Move point to the center site of the + // polygon using the normalized orthogonal vectors next to the + // point and the size as distance to move. + // point + v + size / tan( angle / 2 ) * startVector + $newPoint = clone $vectors[$next]; + $v ->add( + $newPoint + ->scalar( + $size / + tan( + $lastVector->angle( $vectors[$next] ) / 2 + ) + ) + ); + + // A fast guess: If the movement of the point exceeds the length of + // the surrounding edge vectors the angle was to small to perform a + // valid size reducement. In this case we just reduce the length of + // the movement to the minimal length of the surrounding vectors. + // This should fit in most cases. + // + // The correct way to check would be a test, if the calculated + // point is still in the original polygon, but a test for a point + // in a polygon is too expensive. + $movement = $v->length(); + if ( ( $movement > $vectorLength[$last] ) && + ( $movement > $vectorLength[$next] ) ) + { + $v->unify()->scalar( min( $vectorLength[$last], $vectorLength[$next] ) ); + } + + $newPoints[$next] = $v->add( $points[$next] ); + } + + return $newPoints; + } + + /** + * Reduce the size of an ellipse + * + * The method returns a the edgepoints and angles for an ellipse where all + * borders are moved to the inner side of the ellipse by the give $size + * value. + * + * The method returns an + * array ( + * 'center' => (ezcGraphCoordinate) New center point, + * 'start' => (ezcGraphCoordinate) New outer start point, + * 'end' => (ezcGraphCoordinate) New outer end point, + * ) + * + * @param ezcGraphCoordinate $center + * @param float $width + * @param float $height + * @param float $startAngle + * @param float $endAngle + * @param float $size + * @throws ezcGraphReducementFailedException + * @return array + */ + protected function reduceEllipseSize( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, $size ) + { + $oldStartPoint = new ezcGraphVector( + $width * cos( deg2rad( $startAngle ) ) / 2, + $height * sin( deg2rad( $startAngle ) ) / 2 + ); + + $oldEndPoint = new ezcGraphVector( + $width * cos( deg2rad( $endAngle ) ) / 2, + $height * sin( deg2rad( $endAngle ) ) / 2 + ); + + // We always need radian values.. + $degAngle = abs( $endAngle - $startAngle ); + $startAngle = deg2rad( $startAngle ); + $endAngle = deg2rad( $endAngle ); + + // Calculate normalized vectors for the lines spanning the ellipse + $unifiedStartVector = ezcGraphVector::fromCoordinate( $oldStartPoint )->unify(); + $unifiedEndVector = ezcGraphVector::fromCoordinate( $oldEndPoint )->unify(); + $startVector = ezcGraphVector::fromCoordinate( $oldStartPoint ); + $endVector = ezcGraphVector::fromCoordinate( $oldEndPoint ); + + $oldStartPoint->add( $center ); + $oldEndPoint->add( $center ); + + // Use orthogonal vectors of normalized ellipse spanning vectors to + $v = clone $unifiedStartVector; + $v->rotateClockwise()->scalar( $size ); + + // calculate new center point + // center + v + size / tan( angle / 2 ) * startVector + $centerMovement = clone $unifiedStartVector; + $newCenter = $v->add( $centerMovement->scalar( $size / tan( ( $endAngle - $startAngle ) / 2 ) ) )->add( $center ); + + // Test if center is still inside the ellipse, otherwise the sector + // was to small to be reduced + $innerBoundingBoxSize = 0.7 * min( $width, $height ); + if ( ( $newCenter->x < ( $center->x + $innerBoundingBoxSize ) ) && + ( $newCenter->x > ( $center->x - $innerBoundingBoxSize ) ) && + ( $newCenter->y < ( $center->y + $innerBoundingBoxSize ) ) && + ( $newCenter->y > ( $center->y - $innerBoundingBoxSize ) ) ) + { + // Point is in inner bounding box -> everything is OK + } + elseif ( ( $newCenter->x < ( $center->x - $width ) ) || + ( $newCenter->x > ( $center->x + $width ) ) || + ( $newCenter->y < ( $center->y - $height ) ) || + ( $newCenter->y > ( $center->y + $height ) ) ) + { + // Quick outer boundings check + if ( $degAngle > 180 ) + { + // Use old center for very big angles + $newCenter = clone $center; + } + else + { + // Do not draw for very small angles + throw new ezcGraphReducementFailedException(); + } + } + else + { + // Perform exact check + $distance = new ezcGraphVector( + $newCenter->x - $center->x, + $newCenter->y - $center->y + ); + + // Convert elipse to circle for correct angle calculation + $direction = clone $distance; + $direction->y *= ( $width / $height ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $outerPoint = new ezcGraphVector( + sin( $angle ) * $width / 2, + cos( $angle ) * $height / 2 + ); + + // Point is not in ellipse any more + if ( abs( $distance->x ) > abs( $outerPoint->x ) ) + { + if ( $degAngle > 180 ) + { + // Use old center for very big angles + $newCenter = clone $center; + } + else + { + // Do not draw for very small angles + throw new ezcGraphReducementFailedException(); + } + } + } + + // Use start spanning vector and its orthogonal vector to calculate + // new start point + $newStartPoint = clone $oldStartPoint; + + // Create tangent vector from tangent angle + + // Ellipse tangent factor + $ellipseTangentFactor = sqrt( + pow( $height, 2 ) * + pow( cos( $startAngle ), 2 ) + + pow( $width, 2 ) * + pow( sin( $startAngle ), 2 ) + ); + $ellipseTangentVector = new ezcGraphVector( + $width * -sin( $startAngle ) / $ellipseTangentFactor, + $height * cos( $startAngle ) / $ellipseTangentFactor + ); + + // Reverse spanning vector + $innerVector = clone $unifiedStartVector; + $innerVector->scalar( $size )->scalar( -1 ); + + $newStartPoint->add( $innerVector)->add( $ellipseTangentVector->scalar( $size ) ); + $newStartVector = clone $startVector; + $newStartVector->add( $ellipseTangentVector ); + + // Use end spanning vector and its orthogonal vector to calculate + // new end point + $newEndPoint = clone $oldEndPoint; + + // Create tangent vector from tangent angle + + // Ellipse tangent factor + $ellipseTangentFactor = sqrt( + pow( $height, 2 ) * + pow( cos( $endAngle ), 2 ) + + pow( $width, 2 ) * + pow( sin( $endAngle ), 2 ) + ); + $ellipseTangentVector = new ezcGraphVector( + $width * -sin( $endAngle ) / $ellipseTangentFactor, + $height * cos( $endAngle ) / $ellipseTangentFactor + ); + + // Reverse spanning vector + $innerVector = clone $unifiedEndVector; + $innerVector->scalar( $size )->scalar( -1 ); + + $newEndPoint->add( $innerVector )->add( $ellipseTangentVector->scalar( $size )->scalar( -1 ) ); + $newEndVector = clone $endVector; + $newEndVector->add( $ellipseTangentVector ); + + return array( + 'center' => $newCenter, + 'start' => $newStartPoint, + 'end' => $newEndPoint, + 'startAngle' => rad2deg( $startAngle + $startVector->angle( $newStartVector ) ), + 'endAngle' => rad2deg( $endAngle - $endVector->angle( $newEndVector ) ), + ); + } + + /** + * 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 + */ + abstract public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ); + + /** + * 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 + */ + abstract public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ); + + /** + * 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 + */ + abstract protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text ); + + /** + * Test if string fits in a box with given font size + * + * This method splits the text up into tokens and tries to wrap the text + * in an optimal way to fit in the Box defined by width and height. + * + * If the text fits into the box an array with lines is returned, which + * can be used to render the text later: + * array( + * // Lines + * array( 'word', 'word', .. ), + * ) + * Otherwise the function will return false. + * + * @param string $string Text + * @param ezcGraphCoordinate $position Topleft position of the text box + * @param float $width Width of textbox + * @param float $height Height of textbox + * @param int $size Fontsize + * @return mixed Array with lines or false on failure + */ + protected function testFitStringInTextBox( $string, ezcGraphCoordinate $position, $width, $height, $size ) + { + // Tokenize String + $tokens = preg_split( '/\s+/', $string ); + $initialHeight = $height; + + $lines = array( array() ); + $line = 0; + foreach ( $tokens as $nr => $token ) + { + // Add token to tested line + $selectedLine = $lines[$line]; + $selectedLine[] = $token; + + $boundings = $this->getTextBoundings( $size, $this->options->font, implode( ' ', $selectedLine ) ); + // Check if line is too long + if ( $boundings->width > $width ) + { + if ( count( $selectedLine ) == 1 ) + { + // Return false if one single word does not fit into one line + // Scale down font size to fit this word in one line + return $width / $boundings->width; + } + 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 1; + } + } + + // Check width of last line + $boundings = $this->getTextBoundings( $size, $this->options->font, implode( ' ', $lines[$line] ) ); + if ( $boundings->width > $width ) + { + return 1; + } + + // It seems to fit - return line array + return $lines; + } + + /** + * If it is allow to shortened the string, this method tries to extract as + * many chars as possible to display a decent amount of characters. + * + * If no complete token (word) does fit, the largest possible amount of + * chars from the first word are taken. If the amount of chars is bigger + * then strlen( shortenedStringPostFix ) * 2 the last chars are replace by + * the postfix. + * + * If one complete word fits the box as many words are taken as possible + * including a appended shortenedStringPostFix. + * + * @param mixed $string + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @param mixed $size + * @access protected + * @return void + */ + protected function tryFitShortenedString( $string, ezcGraphCoordinate $position, $width, $height, $size ) + { + $tokens = preg_split( '/\s+/', $string ); + + // Try to fit a complete word first + $boundings = $this->getTextBoundings( + $size, + $this->options->font, + reset( $tokens ) . ( $postfix = $this->options->autoShortenStringPostFix ) + ); + + if ( $boundings->width > $width ) + { + // Not even one word fits the box + $word = reset( $tokens ); + + // Test if first character fits the box + $boundigs = $this->getTextBoundings( + $size, + $this->options->font, + $hit = $word[0] + ); + + if ( $boundigs->width > $width ) + { + // That is a really small box. + throw new ezcGraphFontRenderingException( $string, $size, $width, $height ); + } + + // Try to put more charactes in there + $postLength = strlen( $postfix ); + $wordLength = strlen( $word ); + for ( $i = 2; $i <= $wordLength; ++$i ) + { + $string = substr( $word, 0, $i ); + if ( strlen( $string ) > ( $postLength << 1 ) ) + { + $string = substr( $string, 0, -$postLength ) . $postfix; + } + + $boundigs = $this->getTextBoundings( $size, $this->options->font, $string ); + + if ( $boundigs->width < $width ) + { + $hit = $string; + } + else + { + // Use last string which fit + break; + } + } + } + else + { + // Try to use as many words as possible + $hit = reset( $tokens ); + + for ( $i = 2; $i < count( $tokens ); ++$i ) + { + $string = implode( ' ', array_slice( $tokens, 0, $i ) ) . + $postfix; + + $boundings = $this->getTextBoundings( $size, $this->options->font, $string ); + + if ( $boundings->width <= $width ) + { + $hit .= ' ' . $tokens[$i - 1]; + } + else + { + // Use last valid hit + break; + } + } + + $hit .= $postfix; + } + + return array( array( $hit ) ); + } + + /** + * 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 + */ + abstract public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ); + + /** + * 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 + */ + abstract public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = 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 Fill state + * @return void + */ + abstract public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ); + + /** + * 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 + */ + abstract public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ); + + /** + * Draw an image + * + * @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 + */ + abstract public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ); + + /** + * Return mime type for current image format + * + * @return string + */ + abstract public function getMimeType(); + + /** + * 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( 'php://output' ); + } + + /** + * Finally save image + * + * @param string $file Destination filename + * @return void + */ + abstract public function render( $file ); +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/element.php b/src/TUnit/external/ezc/Graph/interfaces/element.php new file mode 100644 index 0000000..52233a0 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/element.php @@ -0,0 +1,342 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->data['example'] = new ezcGraphArrayDataSet( array( + * 'Foo' => 23, + * 'Bar' => 42, + * ) ); + * + * // Set a title and format the title element + * $graph->title = 'Example formatted pie chart'; + * $graph->title->margin = 2; + * $graph->title->background = '#FFFFFF80'; + * $graph->title->border = '#FFFFFF'; + * $graph->title->borderWidth = 1; + * $graph->title->margin = 1; + * $graph->title->padding = 1; + * + * // Format the legend element + * $graph->legend->margin = 2; + * $graph->legend->background = '#FFFFFF80'; + * $graph->legend->border = '#FFFFFF'; + * $graph->legend->borderWidth = 1; + * $graph->legend->margin = 1; + * $graph->legend->padding = 1; + * + * $graph->background->background = '#888a85'; + * + * $graph->render( 400, 250, 'element.svg' ); + * + * + * @property string $title + * Title of chart element. + * @property ezcGraphColor $background + * Background color of chart element. + * @property ezcGraphColor $border + * Border color of chart element. + * @property int $padding + * Distance between border and content of element. + * @property int $margin + * Distance between outer boundings and border of an element. + * @property int $borderWidth + * Border width. + * @property int $position + * Integer defining the elements position in the chart. + * @property int $maxTitleHeight + * Maximum size of the title. + * @property float $portraitTitleSize + * Percentage of boundings which are used for the title with + * position left, right or center. + * @property float $landscapeTitleSize + * Percentage of boundings which are used for the title with + * position top or bottom. + * @property ezcGraphFontOptions $font + * Font used for this element. + * @property-read bool $fontCloned + * Indicates if font configuration was already cloned for this + * specific element. + * @property-read ezcGraphBoundings $boundings + * Boundings of this elements. + * + * @version 1.4.3 + * @package Graph + */ +abstract class ezcGraphChartElement extends ezcBaseOptions +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['title'] = false; + $this->properties['background'] = false; + $this->properties['boundings'] = new ezcGraphBoundings(); + $this->properties['border'] = false; + $this->properties['borderWidth'] = 0; + $this->properties['padding'] = 0; + $this->properties['margin'] = 0; + $this->properties['position'] = ezcGraph::LEFT; + $this->properties['maxTitleHeight'] = 16; + $this->properties['portraitTitleSize'] = .15; + $this->properties['landscapeTitleSize'] = .2; + $this->properties['font'] = new ezcGraphFontOptions(); + $this->properties['fontCloned'] = false; + + parent::__construct( $options ); + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->properties['border'] = $palette->elementBorderColor; + $this->properties['borderWidth'] = $palette->elementBorderWidth; + $this->properties['background'] = $palette->elementBackground; + $this->properties['padding'] = $palette->padding; + $this->properties['margin'] = $palette->margin; + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'title': + $this->properties['title'] = (string) $propertyValue; + break; + case 'background': + $this->properties['background'] = ezcGraphColor::create( $propertyValue ); + break; + case 'border': + $this->properties['border'] = ezcGraphColor::create( $propertyValue ); + break; + case 'padding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['padding'] = (int) $propertyValue; + break; + case 'margin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['margin'] = (int) $propertyValue; + break; + case 'borderWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['borderWidth'] = (int) $propertyValue; + break; + case 'font': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['font'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->fontCloned ) + { + $this->properties['font'] = clone $this->font; + $this->properties['fontCloned'] = true; + } + + $this->properties['font']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + case 'position': + $positions = array( + ezcGraph::TOP, + ezcGraph::BOTTOM, + ezcGraph::LEFT, + ezcGraph::RIGHT, + ); + + if ( in_array( $propertyValue, $positions, true ) ) + { + $this->properties['position'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( 'position', $propertyValue, 'integer' ); + } + break; + case 'maxTitleHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['maxTitleHeight'] = (int) $propertyValue; + break; + case 'portraitTitleSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['portraitTitleSize'] = (float) $propertyValue; + break; + case 'landscapeTitleSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['landscapeTitleSize'] = (float) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * __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 'font': + // Clone font configuration when requested for this element + if ( !$this->fontCloned ) + { + $this->properties['font'] = clone $this->properties['font']; + $this->properties['fontCloned'] = true; + } + return $this->properties['font']; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Renders this chart element + * + * This method receives and returns a part of the canvas where it can be + * rendered on. + * + * @param ezcGraphRenderer $renderer + * @param ezcGraphBoundings $boundings + * @return ezcGraphBoundings Part of canvas, which is still free to draw on + */ + abstract public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ); + + /** + * Returns calculated boundings based on available percentual space of + * given bounding box specified in the elements options and direction of + * the box. + * + * @param ezcGraphBoundings $boundings + * @param int $direction + * @return ezcGraphBoundings + */ + protected function getTitleSize( ezcGraphBoundings $boundings, $direction = ezcGraph::HORIZONTAL ) + { + if ( $direction === ezcGraph::HORIZONTAL ) + { + return min( + $this->maxTitleHeight, + ( $boundings->y1 - $boundings->y0 ) * $this->landscapeTitleSize + ); + } + else + { + return min( + $this->maxTitleHeight, + ( $boundings->y1 - $boundings->y0 ) * $this->portraitTitleSize + ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/odometer_renderer.php b/src/TUnit/external/ezc/Graph/interfaces/odometer_renderer.php new file mode 100644 index 0000000..ed32060 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/odometer_renderer.php @@ -0,0 +1,51 @@ + diff --git a/src/TUnit/external/ezc/Graph/interfaces/palette.php b/src/TUnit/external/ezc/Graph/interfaces/palette.php new file mode 100644 index 0000000..fbaa845 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/palette.php @@ -0,0 +1,284 @@ +colorIndex = -1; + $this->symbolIndex = -1; + } + + /** + * Returns the requested property + * + * @param string $propertyName Name of property + * @return mixed + * @ignore + */ + public function __get( $propertyName ) + { + switch ( $propertyName ) + { + case 'axisColor': + case 'majorGridColor': + case 'minorGridColor': + case 'fontColor': + case 'chartBackground': + case 'chartBorderColor': + case 'elementBackground': + case 'elementBorderColor': + return ( $this->$propertyName = $this->checkColor( $this->$propertyName ) ); + + case 'dataSetColor': + $this->colorIndex = ( ( $this->colorIndex + 1 ) % count( $this->dataSetColor ) ); + return $this->checkColor( $this->dataSetColor[ $this->colorIndex ] ); + case 'dataSetSymbol': + $this->symbolIndex = ( ( $this->symbolIndex + 1 ) % count( $this->dataSetSymbol ) ); + return $this->dataSetSymbol[ $this->symbolIndex ]; + + case 'fontName': + case 'chartBorderWidth': + case 'elementBorderWidth': + case 'padding': + case 'margin': + return $this->$propertyName; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * __set + * + * @param mixed $propertyName Property name + * @param mixed $propertyValue Property value + * @access public + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'axisColor': + case 'majorGridColor': + case 'minorGridColor': + case 'fontColor': + case 'chartBackground': + case 'chartBorderColor': + case 'elementBackground': + case 'elementBorderColor': + $this->$propertyName = ezcGraphColor::create( $propertyValue ); + break; + + case 'dataSetColor': + if ( !is_array( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'array( ezcGraphColor )' ); + } + + $this->dataSetColor = array(); + foreach ( $propertyValue as $value ) + { + $this->dataSetColor[] = ezcGraphColor::create( $value ); + } + $this->colorIndex = -1; + break; + case 'dataSetSymbol': + if ( !is_array( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'array( (int) ezcGraph::SYMBOL_TYPE )' ); + } + + $this->dataSetSymbol = array(); + foreach ( $propertyValue as $value ) + { + $this->dataSetSymbol[] = (int) $value; + } + $this->symbolIndex = -1; + break; + + case 'fontName': + $this->$propertyName = (string) $propertyValue; + break; + + case 'chartBorderWidth': + case 'elementBorderWidth': + case 'padding': + case 'margin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->$propertyName = (int) $propertyValue; + break; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/radar_renderer.php b/src/TUnit/external/ezc/Graph/interfaces/radar_renderer.php new file mode 100644 index 0000000..be51816 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/radar_renderer.php @@ -0,0 +1,54 @@ + diff --git a/src/TUnit/external/ezc/Graph/interfaces/renderer.php b/src/TUnit/external/ezc/Graph/interfaces/renderer.php new file mode 100644 index 0000000..de1418e --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/renderer.php @@ -0,0 +1,717 @@ + + * $chart = new ezcGraphPieChart(); + * $chart->driver = new ezcGraphSvgDriver(); + * + * + * @param ezcGraphDriver $driver Output driver + * @return void + */ + public function setDriver( ezcGraphDriver $driver ) + { + $this->driver = $driver; + } + + /** + * Adds a element reference for context + * + * @param ezcGraphContext $context Dataoint context + * @param mixed $reference Driver dependant reference + * @return void + */ + protected function addElementReference( ezcGraphContext $context, $reference ) + { + $this->elements['data'][$context->dataset][$context->datapoint][] = $reference; + } + + /** + * Return all chart element references + * + * Returns element references for the data sets in the chart, so the + * created graphic may be enhanced later. + * + * The resulting array looks like: + * + * array ( + * legend_url => array ( + * $name => $url | null, + * ... + * ), + * legend => array ( + * $name => $data, + * ... + * ) + * data => array ( + * $dataset => array ( + * $name => $data, + * ... + * ), + * ... + * ) + * ) + * + * + * The legend elements won't show up in the array, if there is no legend + * redered. The URLs are only available, if the url property has been set + * on the respective dataset. + * + * The data assigned to the legends and data elements is completely direver + * dependent. In the SVG and Flash driver there will jsut be some IDs, + * which allow you to reference the affected elements or element groups + * inside the flash or SVG file. + * + * For bitmap formats, like in the Cairo or GD driver, $data will be an + * array of ezcGraphCoordinate objects, which roughly describe the outline + * of the referenced element. For circles and alike the resolution of this + * outline can be configured in the respective driver. + * + * @return array + */ + public function getElementReferences() + { + return $this->elements; + } + + /** + * __get + * + * @param string $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 'xAxisSpace': + case 'yAxisSpace': + return $this->$propertyName; + case 'elements': + return $this->elements; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * 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 + */ + abstract public function drawPieSegment( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + $startAngle = .0, + $endAngle = 360., + $label = false, + $moveOut = false + ); + + /** + * 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 + */ + abstract public function drawBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $stepSize, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. + ); + + /** + * 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 + */ + abstract public function drawDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $axisPosition = 0., + $thickness = 1. + ); + + /** + * 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 + */ + abstract 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 + ); + + /** + * 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 + */ + abstract public function drawLegend( + ezcGraphBoundings $boundings, + ezcGraphChartElementLegend $legend, + $type = ezcGraph::VERTICAL + ); + + /** + * 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 + */ + abstract public function drawBox( + ezcGraphBoundings $boundings, + ezcGraphColor $background = null, + ezcGraphColor $borderColor = null, + $borderWidth = 0, + $margin = 0, + $padding = 0, + $title = false, + $titleSize = 16 + ); + + /** + * 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 + */ + abstract public function drawText( + ezcGraphBoundings $boundings, + $text, + $align = ezcGraph::LEFT, + ezcGraphRotation $rotation = null + ); + + /** + * 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 + */ + abstract public function drawAxis( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphAxisLabelRenderer $labelClass = null + ); + + /** + * Draw axis arrow head + * + * Draw an arrow head at the specified position using specified size + * and direction of the error head. Repsects the axisEndStyle option in + * the base renderer options class. + * + * @param ezcGraphCoordinate $position + * @param ezcGraphVector $direction + * @param float $size + * @param ezcGraphColor $color + * @return void + */ + protected function drawAxisArrowHead( ezcGraphCoordinate $position, ezcGraphVector $direction, $size, ezcGraphColor $color ) + { + $orthogonalDirection = clone $direction; + $orthogonalDirection->rotateClockwise(); + + if ( $this->options->axisEndStyle === ezcGraph::ARROW ) + { + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $position->x, + $position->y + ), + new ezcGraphCoordinate( + $position->x + - $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $position->y + - $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + new ezcGraphCoordinate( + $position->x + + $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $position->y + + $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + ), + $color, + true + ); + } + elseif ( $this->options->axisEndStyle !== ezcGraph::NO_SYMBOL ) + { + $topLeft = new ezcGraphCoordinate( + $position->x + + $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $position->y + + $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ); + + $bottomRight = new ezcGraphCoordinate( + $position->x + - $orthogonalDirection->x * $size / 2, + $position->y + - $orthogonalDirection->y * $size / 2 + ); + + $this->drawSymbol( + $boundings = new ezcGraphBoundings( + min( $topLeft->x, $bottomRight->x ), + min( $topLeft->y, $bottomRight->y ), + max( $topLeft->x, $bottomRight->x ), + max( $topLeft->y, $bottomRight->y ) + ), + $color, + $this->options->axisEndStyle + ); + } + } + + /** + * 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 + */ + abstract public function drawBackgroundImage( + ezcGraphBoundings $boundings, + $file, + $position = 48, // ezcGraph::CENTER | ezcGraph::MIDDLE + $repeat = ezcGraph::NO_REPEAT + ); + + /** + * Draw Symbol + * + * Draws a single symbol defined by the symbol constants in ezcGraph. for + * NO_SYMBOL a rect will be drawn. + * + * @param ezcGraphBoundings $boundings Boundings of symbol + * @param ezcGraphColor $color Color of symbol + * @param int $symbol Type of symbol + * @return void + */ + public function drawSymbol( + ezcGraphBoundings $boundings, + ezcGraphColor $color, + $symbol = ezcGraph::NO_SYMBOL ) + { + switch ( $symbol ) + { + case ezcGraph::NO_SYMBOL: + case ezcGraph::SQUARE: + $return = $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 ), + ), + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawPolygon( + array( + $topLeft = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + $bottomRight = new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + ), + new ezcGraphLinearGradient( + $bottomRight, + $topLeft, + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::BOX: + $return = $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 ), + ), + $color, + false + ); + return $return; + case ezcGraph::DIAMOND: + $return = $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ), + new ezcGraphCoordinate( + $boundings->x1, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y1 + ), + new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ), + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ), + new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.353553391, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.353553391 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * ( 1 - 0.353553391 ), + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * ( 1 - 0.353553391 ) + ), + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::BULLET: + $return = $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + $boundings->x1 - $boundings->x0, + $boundings->y1 - $boundings->y0, + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize, + new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.292893219, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.292893219 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.707106781, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.707106781 + ), + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::CIRCLE: + return $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + $boundings->x1 - $boundings->x0, + $boundings->y1 - $boundings->y0, + $color, + false + ); + } + } + + /** + * Finish rendering + * + * Method is called before the final image is renderer, so that finishing + * operations can be performed here. + * + * @return void + */ + abstract protected function finish(); + + /** + * 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() + { + $this->xAxisSpace = false; + $this->yAxisSpace = false; + + // Reset driver, maintaining its configuration + $driverClass = get_class( $this->driver ); + $driverOptions = $this->driver->options; + $this->driver = new $driverClass(); + $this->driver->options = $driverOptions; + } + + /** + * Finally renders the image + * + * @param string $file Filename of destination file + * @return void + */ + public function render( $file = null ) + { + $this->finish(); + + if ( $file === null ) + { + $this->driver->renderToOutput(); + } + else + { + $this->driver->render( $file ); + } + + $this->resetRenderer(); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/interfaces/stacked_bar_renderer.php b/src/TUnit/external/ezc/Graph/interfaces/stacked_bar_renderer.php new file mode 100644 index 0000000..1508bc4 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/interfaces/stacked_bar_renderer.php @@ -0,0 +1,46 @@ + diff --git a/src/TUnit/external/ezc/Graph/math/boundings.php b/src/TUnit/external/ezc/Graph/math/boundings.php new file mode 100644 index 0000000..948b66d --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/boundings.php @@ -0,0 +1,109 @@ +x0 = $x0; + $this->y0 = $y0; + $this->x1 = $x1; + $this->y1 = $y1; + + // Switch values to ensure correct order + if ( $this->x0 > $this->x1 ) + { + $tmp = $this->x0; + $this->x0 = $this->x1; + $this->x1 = $tmp; + } + + if ( $this->y0 > $this->y1 ) + { + $tmp = $this->y0; + $this->y0 = $this->y1; + $this->y1 = $tmp; + } + } + + /** + * Getter for calculated values depending on the boundings. + * - 'width': Width of bounding recangle + * - 'height': Height of bounding recangle + * + * @param string $name Name of property to get + * @return mixed Calculated value + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'width': + return $this->x1 - $this->x0; + case 'height': + return $this->y1 - $this->y0; + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/math/matrix.php b/src/TUnit/external/ezc/Graph/math/matrix.php new file mode 100644 index 0000000..24f039b --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/matrix.php @@ -0,0 +1,511 @@ +rows = max( 1, (int) $rows ); + $this->columns = max( 1, (int) $columns ); + + if ( $values !== null ) + { + $this->fromArray( $values ); + } + else + { + $this->init(); + } + } + + /** + * Create matrix from array + * + * Use an array with float values to set matrix values. + * + * @param array $values Array with values + * @return ezcGraphMatrix Modified matrix + */ + public function fromArray( array $values ) + { + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = + ( isset( $values[$i][$j] ) + ? (float) $values[$i][$j] + : 0 ); + } + } + + return $this; + } + + /** + * Init matrix + * + * Sets matrix to identity matrix. + * + * @return ezcGraphMatrix Modified matrix + */ + public function init() + { + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = ( $i === $j ? 1 : 0 ); + } + } + + return $this; + } + + /** + * Returns number of rows + * + * @return int Number of rows + */ + public function rows() + { + return $this->rows; + } + + /** + * Returns number of columns + * + * @return int Number of columns + */ + public function columns() + { + return $this->columns; + } + + /** + * Get a single matrix value + * + * Returns the value of the matrix at the given position + * + * @param int $i Column + * @param int $j Row + * @return float Matrix value + */ + public function get( $i, $j ) + { + if ( ( $i < 0 ) || + ( $i >= $this->rows ) || + ( $j < 0 ) || + ( $j >= $this->columns ) ) + { + throw new ezcGraphMatrixOutOfBoundingsException( $this->rows, $this->columns, $i, $j ); + } + + return ( !isset( $this->matrix[$i][$j] ) ? .0 : $this->matrix[$i][$j] ); + } + + /** + * Set a single matrix value + * + * Sets the value of the matrix at the given position. + * + * @param int $i Column + * @param int $j Row + * @param float $value Value + * @return ezcGraphMatrix Updated matrix + */ + public function set( $i, $j, $value ) + { + if ( ( $i < 0 ) || + ( $i >= $this->rows ) || + ( $j < 0 ) || + ( $j >= $this->columns ) ) + { + throw new ezcGraphMatrixOutOfBoundingsException( $this->rows, $this->columns, $i, $j ); + } + + $this->matrix[$i][$j] = $value; + + return $this; + } + + /** + * Adds one matrix to the current one + * + * Calculate the sum of two matrices and returns the resulting matrix. + * + * @param ezcGraphMatrix $matrix Matrix to sum with + * @return ezcGraphMatrix Result matrix + */ + public function add( ezcGraphMatrix $matrix ) + { + if ( ( $this->rows !== $matrix->rows() ) || + ( $this->columns !== $matrix->columns() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->rows, $this->columns, $matrix->rows(), $matrix->columns() ); + } + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] += $matrix->get( $i, $j ); + } + } + + return $this; + } + + /** + * Subtracts matrix from current one + * + * Calculate the diffenrence of two matices and returns the result matrix. + * + * @param ezcGraphMatrix $matrix subtrahend + * @return ezcGraphMatrix Result matrix + */ + public function diff( ezcGraphMatrix $matrix ) + { + if ( ( $this->rows !== $matrix->rows() ) || + ( $this->columns !== $matrix->columns() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->rows, $this->columns, $matrix->rows(), $matrix->columns() ); + } + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] -= $matrix->get( $i, $j ); + } + } + + return $this; + } + + /** + * Scalar multiplication + * + * Multiplies matrix with the given scalar and returns the result matrix + * + * @param float $scalar Scalar + * @return ezcGraphMatrix Result matrix + */ + public function scalar( $scalar ) + { + $scalar = (float) $scalar; + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] *= $scalar; + } + } + } + + /** + * Transpose matrix + * + * @return ezcGraphMatrix Transposed matrix + */ + public function transpose() + { + $matrix = clone $this; + + $this->rows = $matrix->columns(); + $this->columns = $matrix->rows(); + + $this->matrix = array(); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = $matrix->get( $j, $i ); + } + } + + return $this; + } + + /** + * Multiplies two matrices + * + * Multiply current matrix with another matrix and returns the result + * matrix. + * + * @param ezcGraphMatrix $matrix Second factor + * @return ezcGraphMatrix Result matrix + */ + public function multiply( ezcGraphMatrix $matrix ) + { + $mColumns = $matrix->columns(); + if ( $this->columns !== ( $mRows = $matrix->rows() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->columns, $this->rows, $mColumns, $mRows ); + } + + $result = new ezcGraphMatrix( $this->rows, $mColumns ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $mColumns; ++$j ) + { + $sum = 0; + for ( $k = 0; $k < $mRows; ++$k ) { + $sum += $this->matrix[$i][$k] * $matrix->get( $k, $j ); + } + + $result->set( $i, $j, $sum ); + } + } + + return $result; + } + + /** + * Solve nonlinear equatation + * + * Tries to solve equatation given by two matrices, with assumption, that: + * A * x = B + * where $this is A, and the paramenter B. x is cosnidered as a vector + * x = ( x^n, x^(n-1), ..., x^2, x, 1 ) + * + * Will return a polynomial solution for x. + * + * See: http://en.wikipedia.org/wiki/Gauss-Newton_algorithm + * + * @param ezcGraphMatrix $matrix B + * @return ezcGraphPolygon Solution of equatation + */ + public function solveNonlinearEquatation( ezcGraphMatrix $matrix ) + { + // Build complete equatation + $equatation = new ezcGraphMatrix( $this->rows, $columns = ( $this->columns + 1 ) ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $equatation->set( $i, $j, $this->matrix[$i][$j] ); + } + $equatation->set( $i, $this->columns, $matrix->get( $i, 0 ) ); + } + + // Compute upper triangular matrix on left side of equatation + for ( $i = 0; $i < ( $this->rows - 1 ); ++$i ) + { + for ( $j = $i + 1; $j < $this->rows; ++$j ) + { + if ( $equatation->get( $j, $i ) !== 0 ) + { + if ( $equatation->get( $j, $i ) == 0 ) + { + continue; + } + else + { + $factor = -( $equatation->get( $i, $i ) / $equatation->get( $j, $i ) ); + } + + for ( $k = $i; $k < $columns; ++$k ) + { + $equatation->set( $j, $k, $equatation->get( $i, $k ) + $factor * $equatation->get( $j, $k ) ); + } + } + } + } + + // Normalize values on left side matrix diagonale + for ( $i = 0; $i < $this->rows; ++$i ) + { + if ( ( ( $value = $equatation->get( $i, $i ) ) != 1 ) && + ( $value != 0 ) ) + { + $factor = 1 / $value; + for ( $k = $i; $k < $columns; ++$k ) + { + $equatation->set( $i, $k, $equatation->get( $i, $k ) * $factor ); + } + } + } + + // Build up solving polynom + $polynom = new ezcGraphPolynom(); + for ( $i = ( $this->rows - 1 ); $i >= 0; --$i ) + { + for ( $j = $i + 1; $j < $this->columns; ++$j ) + { + $equatation->set( + $i, + $this->columns, + $equatation->get( $i, $this->columns ) + ( -$equatation->get( $i, $j ) * $polynom->get( $j ) ) + ); + $equatation->set( $i, $j, 0 ); + } + $polynom->set( $i, $equatation->get( $i, $this->columns ) ); + } + + return $polynom; + } + + /** + * Build LR decomposition from matrix + * + * Use Cholesky-Crout algorithm to get LR decomposition of the current + * matrix. + * + * Will return an array with two matrices: + * array( + * 'l' => (ezcGraphMatrix) $left, + * 'r' => (ezcGraphMatrix) $right, + * ) + * + * @return array( ezcGraphMatrix ) + */ + public function LRdecomposition() + { + /** + * Use Cholesky-Crout algorithm to get LR decomposition + * + * Input: Matrix A ($this) + * + * For i = 1 To n + * For j = i To n + * R(i,j)=A(i,j) + * For k = 1 TO i-1 + * R(i,j)-=L(i,k)*R(k,j) + * end + * end + * For j=i+1 To n + * L(j,i)= A(j,i) + * For k = 1 TO i-1 + * L(j,i)-=L(j,k)*R(k,i) + * end + * L(j,i)/=R(i,i) + * end + * end + * + * Output: matrices L,R + */ + $l = new ezcGraphMatrix( $this->columns, $this->rows ); + $r = new ezcGraphMatrix( $this->columns, $this->rows ); + + for ( $i = 0; $i < $this->columns; ++$i ) + { + for ( $j = $i; $j < $this->rows; ++$j ) + { + $r->set( $i, $j, $this->matrix[$i][$j] ); + for ( $k = 0; $k <= ( $i - 1 ); ++$k ) + { + $r->set( $i, $j, $r->get( $i, $j ) - $l->get( $i, $k ) * $r->get( $k, $j ) ); + } + } + + for ( $j = $i + 1; $j < $this->rows; ++$j ) + { + $l->set( $j, $i, $this->matrix[$j][$i] ); + for ( $k = 0; $k <= ( $i - 1 ); ++$k ) + { + $l->set( $j, $i, $l->get( $j, $i ) - $l->get( $j, $k ) * $r->get( $k, $i ) ); + } + $l->set( $j, $i, $l->get( $j, $i ) / $r->get( $i, $i ) ); + } + } + + return array( + 'l' => $l, + 'r' => $r, + ); + } + + /** + * Returns a string representation of the matrix + * + * @return string + */ + public function __toString() + { + $string = sprintf( "%d x %d matrix:\n", $this->rows, $this->columns ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + $string .= '| '; + for ( $j = 0; $j < $this->columns; ++$j ) + { + $string .= sprintf( '%04.2f ', $this->get( $i, $j ) ); + } + $string .= "|\n"; + } + + return $string; + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/math/polynom.php b/src/TUnit/external/ezc/Graph/math/polynom.php new file mode 100644 index 0000000..70ad98c --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/polynom.php @@ -0,0 +1,259 @@ + + * // Equivalent to: x^2 + .5 + * $polynom = new ezcGraphPolynom( array( 2 => 1, 0 => .5 ) ); + * + * // Calculate result for x = 1, echos: 1.5 + * echo $polynom->evaluate( 1 ), PHP_EOL; + * + * // Build the sum with another polynom + * $polynom->add( new ezcGraphPolynom( array( 1 => 1 ) ) ); + * + * // Print polynom, echos: + * // x^2 + x + 5.00e-1 + * echo $polynom, PHP_EOL; + * + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphPolynom +{ + /** + * Factors of the polynom + * + * An example: + * Polynom: + * 2 * x^3 + .5 * x - 3 + * Array: + * array ( + * (int) 3 => (float) 2, + * (int) 1 => (float) .5, + * (int) 0 => (float) -3, + * ) + * + * @var array + */ + protected $values; + + // @TODO: Introduce precision option for string output? + + /** + * Constructor + * + * Constructs a polynom object from given array, where the key is the + * exponent and the value the factor. + * An example: + * Polynom: + * 2 * x^3 + .5 * x - 3 + * Array: + * array ( + * (int) 3 => (float) 2, + * (int) 1 => (float) .5, + * (int) 0 => (float) -3, + * ) + * + * @param array $values Array with values + * @return ezcGraphPolynom + */ + public function __construct( array $values = array() ) + { + foreach ( $values as $exponent => $factor ) + { + $this->values[(int) $exponent] = (float) $factor; + } + } + + /** + * Initialise a polygon + * + * Initialise a polygon of the given order. Sets all factors to 0. + * + * @param int $order Order of polygon + * @return ezcGraphPolynom Created polynom + */ + public function init( $order ) + { + for ( $i = 0; $i <= $order; ++$i ) + { + $this->values[$i] = 0; + } + + return $this; + } + + /** + * Return factor for one exponent + * + * @param int $exponent Exponent + * @return float Factor + */ + public function get( $exponent ) + { + if ( !isset( $this->values[$exponent] ) ) + { + return 0; + } + else + { + return $this->values[$exponent]; + } + } + + /** + * Set the factor for one exponent + * + * @param int $exponent Exponent + * @param float $factor Factor + * @return ezcGraphPolynom Modified polynom + */ + public function set( $exponent, $factor ) + { + $this->values[(int) $exponent] = (float) $factor; + + return $this; + } + + /** + * Returns the order of the polynom + * + * @return int Polynom order + */ + public function getOrder() + { + return max( array_keys( $this->values ) ); + } + + /** + * Adds polynom to current polynom + * + * @param ezcGraphPolynom $polynom Polynom to add + * @return ezcGraphPolynom Modified polynom + */ + public function add( ezcGraphPolynom $polynom ) + { + $order = max( + $this->getOrder(), + $polynom->getOrder() + ); + + for ( $i = 0; $i <= $order; ++$i ) + { + $this->set( $i, $this->get( $i ) + $polynom->get( $i ) ); + } + + return $this; + } + + /** + * Evaluate Polynom with a given value + * + * @param float $x Value + * @return float Result + */ + public function evaluate( $x ) + { + $value = 0; + foreach ( $this->values as $exponent => $factor ) + { + $value += $factor * pow( $x, $exponent ); + } + + return $value; + } + + /** + * Returns a string represenation of the polynom + * + * @return string String representation of polynom + */ + public function __toString() + { + krsort( $this->values ); + $string = ''; + + foreach ( $this->values as $exponent => $factor ) + { + if ( $factor == 0 ) + { + continue; + } + + $string .= ( $factor < 0 ? ' - ' : ' + ' ); + + $factor = abs( $factor ); + switch ( true ) + { + case abs( 1 - $factor ) < .0001: + // No not append, if factor is ~1 + break; + case $factor < 1: + case $factor >= 1000: + $string .= sprintf( '%.2e ', $factor ); + break; + case $factor >= 100: + $string .= sprintf( '%.0f ', $factor ); + break; + case $factor >= 10: + $string .= sprintf( '%.1f ', $factor ); + break; + default: + $string .= sprintf( '%.2f ', $factor ); + break; + } + + switch ( true ) + { + case $exponent > 1: + $string .= sprintf( 'x^%d', $exponent ); + break; + case $exponent === 1: + $string .= 'x'; + break; + case $exponent === 0: + if ( abs( 1 - $factor ) < .0001 ) + { + $string .= '1'; + } + break; + } + } + + if ( substr( $string, 0, 3 ) === ' + ' ) + { + $string = substr( $string, 3 ); + } + else + { + $string = '-' . substr( $string, 3 ); + } + + return trim( $string ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/math/rotation.php b/src/TUnit/external/ezc/Graph/math/rotation.php new file mode 100644 index 0000000..0bee1bd --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/rotation.php @@ -0,0 +1,99 @@ +rotation = (float) $rotation; + + if ( $center === null ) + { + $this->center = new ezcGraphCoordinate( 0, 0 ); + + $clockwiseRotation = deg2rad( $rotation ); + $rotationMatrixArray = array( + array( cos( $clockwiseRotation ), -sin( $clockwiseRotation ), 0 ), + array( sin( $clockwiseRotation ), cos( $clockwiseRotation ), 0 ), + array( 0, 0, 1 ), + ); + + return parent::__construct( $rotationMatrixArray ); + } + + parent::__construct(); + + $this->center = $center; + + $this->multiply( new ezcGraphTranslation( $center->x, $center->y ) ); + $this->multiply( new ezcGraphRotation( $rotation ) ); + $this->multiply( new ezcGraphTranslation( -$center->x, -$center->y ) ); + } + + /** + * Return rotaion angle in degrees + * + * @return float + */ + public function getRotation() + { + return $this->rotation; + } + + /** + * Return the center point of the current rotation + * + * @return ezcGraphCoordinate + */ + public function getCenter() + { + return $this->center; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/math/transformation.php b/src/TUnit/external/ezc/Graph/math/transformation.php new file mode 100644 index 0000000..7ff7c8a --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/transformation.php @@ -0,0 +1,96 @@ +columns(); + + // We want to ensure, that the matrix stays 3x3 + if ( ( $this->columns !== $matrix->rows() ) && + ( $this->rows !== $mColumns ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->columns, $this->rows, $mColumns, $matrix->rows() ); + } + + $result = parent::multiply( $matrix ); + + // The matrix dimensions stay the same, so that we can modify $this. + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $mColumns; ++$j ) + { + $this->set( $i, $j, $result->get( $i, $j ) ); + } + } + + return $this; + } + + /** + * Transform a coordinate with the current transformation matrix. + * + * @param ezcGraphCoordinate $coordinate + * @return ezcGraphCoordinate + */ + public function transformCoordinate( ezcGraphCoordinate $coordinate ) + { + $vector = new ezcGraphMatrix( 3, 1, array( array( $coordinate->x ), array( $coordinate->y ), array( 1 ) ) ); + $vector = parent::multiply( $vector ); + + return new ezcGraphCoordinate( $vector->get( 0, 0 ), $vector->get( 1, 0 ) ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/math/translation.php b/src/TUnit/external/ezc/Graph/math/translation.php new file mode 100644 index 0000000..b4f4874 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/translation.php @@ -0,0 +1,47 @@ + diff --git a/src/TUnit/external/ezc/Graph/math/vector.php b/src/TUnit/external/ezc/Graph/math/vector.php new file mode 100644 index 0000000..2f45591 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/math/vector.php @@ -0,0 +1,194 @@ +x; + $this->x = $this->y; + $this->y = -$tmp; + + return $this; + } + + /** + * Rotates vector to the right by 90 degrees + * + * @return void + */ + public function rotateClockwise() + { + $tmp = $this->x; + $this->x = -$this->y; + $this->y = $tmp; + + return $this; + } + + /** + * Unifies vector length to 1 + * + * @return void + */ + public function unify() + { + $length = $this->length(); + if ( $length == 0 ) + { + return $this; + } + + $this->x /= $length; + $this->y /= $length; + + return $this; + } + + /** + * Returns length of vector + * + * @return float + */ + public function length() + { + return sqrt( + pow( $this->x, 2 ) + + pow( $this->y, 2 ) + ); + } + + /** + * Multiplies vector with a scalar + * + * @param float $value + * @return void + */ + public function scalar( $value ) + { + $this->x *= $value; + $this->y *= $value; + + return $this; + } + + /** + * Calculates scalar product of two vectors + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function mul( ezcGraphCoordinate $vector ) + { + return $this->x * $vector->x + $this->y * $vector->y; + } + + /** + * Returns the angle between two vectors in radian + * + * @param ezcGraphCoordinate $vector + * @return float + */ + public function angle( ezcGraphCoordinate $vector ) + { + if ( !$vector instanceof ezcGraphVector ) + { + // Ensure beeing a vector for calling length() + $vector = ezcGraphVector::fromCoordinate( $vector ); + } + + $factor = $this->length() * $vector->length(); + + if ( $factor == 0 ) + { + return false; + } + else + { + return acos( $this->mul( $vector ) / $factor ); + } + } + + /** + * Adds a vector to another vector + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function add( ezcGraphCoordinate $vector ) + { + $this->x += $vector->x; + $this->y += $vector->y; + + return $this; + } + + /** + * Subtracts a vector from another vector + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function sub( ezcGraphCoordinate $vector ) + { + $this->x -= $vector->x; + $this->y -= $vector->y; + + return $this; + } + + /** + * Creates a vector from a coordinate object + * + * @param ezcGraphCoordinate $coordinate + * @return ezcGraphVector + */ + public static function fromCoordinate( ezcGraphCoordinate $coordinate ) + { + return new ezcGraphVector( $coordinate->x, $coordinate->y ); + } + + /** + * Transform vector using transformation matrix + * + * @param ezcGraphTransformation $transformation + * @return ezcGraphVector + */ + public function transform( ezcGraphTransformation $transformation ) + { + $result = $transformation->transformCoordinate( $this ); + + $this->x = $result->x; + $this->y = $result->y; + + return $this; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/cairo_driver.php b/src/TUnit/external/ezc/Graph/options/cairo_driver.php new file mode 100644 index 0000000..7cf7a81 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/cairo_driver.php @@ -0,0 +1,100 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->background->color = '#FFFFFFFF'; + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->driver = new ezcGraphCairoDriver(); + * + * // No options yet. + * + * $graph->render( 400, 200, 'tutorial_driver_cairo.png' ); + * + * + * @property float $imageMapResolution + * Degree step used to interpolate round image primitives by + * polygons for image maps + * @property float $circleResolution + * Resolution for circles, until I understand how to draw ellipses + * with SWFShape::curveTo() + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphCairoDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['imageMapResolution'] = 10; + $this->properties['circleResolution'] = 2.; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'imageMapResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['imageMapResolution'] = (int) $propertyValue; + break; + case 'circleResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['circleResolution'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/chart.php b/src/TUnit/external/ezc/Graph/options/chart.php new file mode 100644 index 0000000..468e5f8 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/chart.php @@ -0,0 +1,107 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzBlue(); + * $graph->title = 'Access statistics'; + * + * // Global font options + * $graph->options->font->name = 'serif'; + * + * // Special font options for sub elements + * $graph->title->background = '#EEEEEC'; + * $graph->title->font->name = 'sans-serif'; + * + * $graph->options->font->maxFontSize = 8; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->render( 400, 150, 'tutorial_chart_title.svg' ); + * + * + * @property int $width + * Width of the chart. + * @property int $height + * Height of the chart. + * @property ezcGraphFontOptions $font + * Font used in the graph. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphChartOptions extends ezcBaseOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['width'] = null; + $this->properties['height'] = null; + $this->properties['font'] = new ezcGraphFontOptions(); + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'width': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['width'] = (int) $propertyValue; + break; + case 'height': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['height'] = (int) $propertyValue; + break; + case 'font': + $this->properties['font']->path = $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/driver.php b/src/TUnit/external/ezc/Graph/options/driver.php new file mode 100644 index 0000000..4b646b5 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/driver.php @@ -0,0 +1,172 @@ + + * require_once 'tutorial_autoload.php'; + * + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzBlue(); + * $graph->title = 'Access statistics'; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * // Do not shorten strings automatically if they do not fit in the assigned + * // space with the minimum font size. + * $graph->driver->options->autoShortenString = false; + * + * $graph->render( 400, 150, 'tutorial_chart_title.svg' ); + * + * + * @property int $width + * Width of the chart. + * @property int $height + * Height of the chart. + * @property float $shadeCircularArc + * Percent to darken circular arcs at the sides + * @property float $lineSpacing + * Percent of font size used for line spacing + * @property int $font + * Font used in the graph. + * @property bool $autoShortenString + * Automatically shorten string if it do not fit into the available + * space, even with the minimum font size used. Deactivating this + * setting will result in ezcGraphFontRenderingException exceptions, + * informing you about the actual string which did not fit. + * @property string $autoShortenStringPostFix + * String to append to shortened strings, if there is enough space + * left for the postfix. + * + * @version 1.4.3 + * @package Graph + */ +abstract class ezcGraphDriverOptions extends ezcBaseOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['width'] = null; + $this->properties['height'] = null; + + $this->properties['lineSpacing'] = .1; + $this->properties['shadeCircularArc'] = .5; + $this->properties['font'] = new ezcGraphFontOptions(); + $this->properties['font']->color = ezcGraphColor::fromHex( '#000000' ); + + $this->properties['autoShortenString'] = true; + $this->properties['autoShortenStringPostFix'] = '..'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'width': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['width'] = (int) $propertyValue; + break; + case 'height': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['height'] = (int) $propertyValue; + break; + case 'lineSpacing': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['lineSpacing'] = (float) $propertyValue; + break; + case 'shadeCircularArc': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['shadeCircularArc'] = (float) $propertyValue; + break; + case 'font': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['font'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + case 'autoShortenString': + if ( is_bool( $propertyValue ) ) + { + $this->properties['autoShortenString'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'boolean' ); + } + break; + case 'autoShortenStringPostFix': + $this->properties['autoShortenStringPostFix'] = (string) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/flash_driver.php b/src/TUnit/external/ezc/Graph/options/flash_driver.php new file mode 100644 index 0000000..6c32bbb --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/flash_driver.php @@ -0,0 +1,107 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->driver = new ezcGraphFlashDriver(); + * $graph->driver->options->compresion = 0; + * + * $graph->options->font = 'tutorial_font.fdb'; + * + * $graph->driver->options->compression = 7; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->render( 400, 200, 'tutorial_driver_flash.swf' ); + * + * + * @property int $compression + * Compression level used for generated flash file + * @see http://php.net/manual/en/function.swfmovie.save.php + * @property float $circleResolution + * Resolution for circles, until I understand how to draw ellipses + * with SWFShape::curveTo() + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphFlashDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['compression'] = 9; + $this->properties['circleResolution'] = 2.; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'compression': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 9 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 9' ); + } + + $this->properties['compression'] = max( 0, min( 9, (int) $propertyValue ) ); + break; + case 'circleResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['circleResolution'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/font.php b/src/TUnit/external/ezc/Graph/options/font.php new file mode 100644 index 0000000..c8dedc0 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/font.php @@ -0,0 +1,312 @@ +options->font. This takes effect on all chart + * elements unless you intentionally access the font configuration of an + * individual chart element. The following example shows, how this works. + * + * + * $graph = new ezcGraphPieChart(); + * $graph->title = 'Access statistics'; + * + * // Set the maximum font size to 8 for all chart elements + * $graph->options->font->maxFontSize = 8; + * + * // Set the font size for the title independently to 14 + * $graph->title->font->maxFontSize = 14; + * + * // The following only affects all elements except the // title element, + * // which now has its own font configuration. + * // + * // Keep in mind that the specified font is driver specific. A pure name + * // works for the SVG driver, used here. The GD driver for example + * // requires a path to a TTF file. + * $graph->options->font->name = 'serif'; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * + * @property string $name + * Name of font. + * @property string $path + * Path to font file. + * @property int $type + * Type of used font. May be one of the following: + * - TTF_FONT Native TTF fonts + * - PS_FONT PostScript Type1 fonts + * - FT2_FONT FreeType 2 fonts + * The type is normally automatically detected when you set the path + * to the font file. + * @property float $minFontSize + * Minimum font size for displayed texts. + * @property float $maxFontSize + * Maximum font size for displayed texts. + * @property float $minimalUsedFont + * The minimal used font size for the current element group. This + * property is set by the driver to maintain this information and + * should not be used to configure the apperance of the chart. See + * $minFontSize instead. + * @property ezcGraphColor $color + * Font color. + * @property ezcGraphColor $background + * Background color. The actual area filled with the background color + * is influenced by the settings $padding and $minimizeBorder. + * @property ezcGraphColor $border + * Border color for the text. The distance between the text and + * border is defined by the properties $padding and $minimizeBorder. + * @property int $borderWidth + * With of the border. To enable the border you need to set the + * $border property to some color. + * @property int $padding + * Padding between text and border. + * @property bool $minimizeBorder + * Fit the border exactly around the text, or use the complete + * possible space. This setting is only relevant, when a border + * color has been set for the font. + * @property bool $textShadow + * Draw shadow for texts. The color of the shadow is defined in + * the property $textShadowColor. + * @property int $textShadowOffset + * Offset for text shadow. This defines the distance the shadow + * is moved to the bottom left relative from the text position. + * @property ezcGraphColor $textShadowColor + * Color of text shadow. If left at the default value "false"" + * the inverse color of the text color will be used. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphFontOptions extends ezcBaseOptions +{ + /** + * Indicates if path already has been checked for correct font + * + * @var bool + */ + protected $pathChecked = false; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['name'] = 'sans-serif'; +// $this->properties['path'] = 'Graph/tests/data/font.ttf'; + $this->properties['path'] = ''; + $this->properties['type'] = ezcGraph::TTF_FONT; + + $this->properties['minFontSize'] = 6; + $this->properties['maxFontSize'] = 96; + $this->properties['minimalUsedFont'] = 96; + $this->properties['color'] = ezcGraphColor::fromHex( '#000000' ); + + $this->properties['background'] = false; + $this->properties['border'] = false; + $this->properties['borderWidth'] = 1; + $this->properties['padding'] = 0; + $this->properties['minimizeBorder'] = true; + + $this->properties['textShadow'] = false; + $this->properties['textShadowOffset'] = 1; + $this->properties['textShadowColor'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'minFontSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 1' ); + } + + // Ensure min font size is smaller or equal max font size. + if ( $propertyValue > $this->properties['maxFontSize'] ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float <= ' . $this->properties['maxFontSize'] ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'maxFontSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 1' ); + } + + // Ensure max font size is greater or equal min font size. + if ( $propertyValue < $this->properties['minFontSize'] ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float >= ' . $this->properties['minFontSize'] ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'minimalUsedFont': + $propertyValue = (float) $propertyValue; + if ( $propertyValue < $this->minimalUsedFont ) + { + $this->properties['minimalUsedFont'] = $propertyValue; + } + break; + + case 'color': + case 'background': + case 'border': + case 'textShadowColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + case 'borderWidth': + case 'padding': + case 'textShadowOffset': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'minimizeBorder': + case 'textShadow': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + $this->properties[$propertyName] = (bool) $propertyValue; + break; + + case 'name': + if ( is_string( $propertyValue ) ) + { + $this->properties['name'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'string' ); + } + break; + case 'path': + if ( is_file( $propertyValue ) && is_readable( $propertyValue ) ) + { + $this->properties['path'] = $propertyValue; + $parts = pathinfo( $this->properties['path'] ); + switch ( strtolower( $parts['extension'] ) ) + { + case 'fdb': + $this->properties['type'] = ezcGraph::PALM_FONT; + break; + case 'pfb': + $this->properties['type'] = ezcGraph::PS_FONT; + break; + case 'ttf': + $this->properties['type'] = ezcGraph::TTF_FONT; + break; + case 'svg': + $this->properties['type'] = ezcGraph::SVG_FONT; + $this->properties['name'] = ezcGraphSvgFont::getFontName( $propertyValue ); + break; + default: + throw new ezcGraphUnknownFontTypeException( $propertyValue, $parts['extension'] ); + } + $this->pathChecked = true; + } + else + { + throw new ezcBaseFileNotFoundException( $propertyValue, 'font' ); + } + break; + case 'type': + if ( is_int( $propertyValue ) ) + { + $this->properties['type'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int' ); + } + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * __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 'textShadowColor': + // Use inverted font color if false + if ( $this->properties['textShadowColor'] === false ) + { + $this->properties['textShadowColor'] = $this->properties['color']->invert(); + } + + return $this->properties['textShadowColor']; + case 'path': + if ( $this->pathChecked === false ) + { + // Enforce call of path check + $this->__set( 'path', $this->properties['path'] ); + } + // No break to use parent return + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/gd_driver.php b/src/TUnit/external/ezc/Graph/options/gd_driver.php new file mode 100644 index 0000000..d03c1bf --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/gd_driver.php @@ -0,0 +1,181 @@ + + * $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 better + * // quality image + * $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' ); + * + * + * @property int $imageFormat + * Type of generated image. + * Should be one of those: IMG_PNG, IMG_JPEG + * @property int $jpegQuality + * Quality of generated jpeg + * @property int $detail + * Count of degrees to render one polygon for in circular arcs + * @property int $supersampling + * Factor of supersampling used to simulate antialiasing + * @property string $background + * Background image to put the graph on + * @property string $resampleFunction + * Function used to resample / resize images + * @property bool $forceNativeTTF + * Force use of native ttf functions instead of free type 2 + * @property float $imageMapResolution + * Degree step used to interpolate round image primitives by + * polygons for image maps + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphGdDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['imageFormat'] = IMG_PNG; + $this->properties['jpegQuality'] = 70; + $this->properties['detail'] = 1; + $this->properties['shadeCircularArc'] = .5; + $this->properties['supersampling'] = 2; + $this->properties['background'] = false; + $this->properties['resampleFunction'] = 'imagecopyresampled'; + $this->properties['forceNativeTTF'] = false; + $this->properties['imageMapResolution'] = 10; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'imageFormat': + if ( imagetypes() & $propertyValue ) + { + $this->properties['imageFormat'] = (int) $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'Unsupported image type.' ); + } + break; + case 'jpegQuality': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 100 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 100' ); + } + + $this->properties['jpegQuality'] = (int) $propertyValue; + break; + case 'detail': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['detail'] = (int) $propertyValue; + break; + case 'supersampling': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['supersampling'] = (int) $propertyValue; + break; + case 'background': + if ( $propertyValue === false || + ( is_file( $propertyValue ) && is_readable( $propertyValue ) ) ) + { + $this->properties['background'] = realpath( $propertyValue ); + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'readable file' ); + } + break; + case 'resampleFunction': + if ( ezcBaseFeatures::hasFunction( $propertyValue ) ) + { + $this->properties['resampleFunction'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'function' ); + } + break; + case 'forceNativeTTF': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['forceNativeTTF'] = (bool) $propertyValue; + break; + case 'imageMapResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['imageMapResolution'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/line_chart.php b/src/TUnit/external/ezc/Graph/options/line_chart.php new file mode 100644 index 0000000..db83c18 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/line_chart.php @@ -0,0 +1,209 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->title = 'Wikipedia articles'; + * + * $graph->options->fillLines = 220; + * $graph->options->lineThickness = 3; + * + * // Add data + * foreach ( $wikidata as $language => $data ) + * { + * $graph->data[$language] = new ezcGraphArrayDataSet( $data ); + * } + * + * $graph->render( 400, 150, 'tutorial_line_chart.svg' ); + * + * + * @property float $lineThickness + * Thickness of chart lines + * @property mixed $fillLines + * Status wheather the space between line and axis should get filled. + * - FALSE to not fill the space at all. + * - (int) Opacity used to fill up the space with the lines color. + * @property int $symbolSize + * Size of symbols in line chart. + * @property ezcGraphFontOptions $highlightFont + * Font configuration for highlight tests + * @property int $highlightSize + * Size of highlight blocks + * @property bool $highlightLines + * If true, it adds lines to highlight the values position on the + * axis. + * @property true $stackBars + * Stack bars + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphLineChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lineThickness'] = 1; + $this->properties['fillLines'] = false; + $this->properties['symbolSize'] = 8; + $this->properties['highlightFont'] = new ezcGraphFontOptions(); + $this->properties['highlightFontCloned'] = false; + $this->properties['highlightSize'] = 14; + $this->properties['highlightXOffset'] = 0; + $this->properties['highlightYOffset'] = 0; + $this->properties['highlightLines'] = false; + $this->properties['stackBars'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'lineThickness': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'symbolSize': + case 'highlightSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'highlightXOffset': + case 'highlightYOffset': + if ( !is_numeric ( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int' ); + } + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'fillLines': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 255 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= int <= 255' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (int) $propertyValue ); + break; + case 'highlightFont': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['highlightFont'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->font; + $this->properties['highlightFontCloned'] = true; + } + + $this->properties['highlightFont']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + $this->properties['highlightSize'] = max( 1, (int) $propertyValue ); + break; + case 'highlightLines': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['highlightLines'] = $propertyValue; + break; + case 'stackBars': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['stackBars'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'highlightFont': + // Clone font configuration when requested for this element + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->properties['font']; + $this->properties['highlightFontCloned'] = true; + } + return $this->properties['highlightFont']; + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/odometer_chart.php b/src/TUnit/external/ezc/Graph/options/odometer_chart.php new file mode 100644 index 0000000..c97edff --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/odometer_chart.php @@ -0,0 +1,113 @@ + + * $graph = new ezcGraphOdoMeterChart(); + * + * $graph->data['Test'] = new ezcGraphArrayDataSet( array( 0, 1, 23, 30 ) ); + * + * $graph->options->odometerHeight = .3; + * $graph->options->borderColor = '#2e3436'; + * + * $graph->render( 150, 50, 'odometer.svg' ); + * + * + * @property ezcGraphColor $borderColor + * Color of border around odometer chart + * @property int $borderWidth + * Width of border around odometer chart + * @property ezcGraphColor $startColor + * Start color of grdient used as the odometer chart background. + * @property ezcGraphColor $endColor + * End color of grdient used as the odometer chart background. + * @property int $markerWidth + * Width of odometer markers + * @property float $odometerHeight + * Height consumed by odometer chart + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphOdometerChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['borderColor'] = ezcGraphColor::create( '#000000' ); + $this->properties['borderWidth'] = 0; + + $this->properties['startColor'] = ezcGraphColor::create( '#4e9a06A0' ); + $this->properties['endColor'] = ezcGraphColor::create( '#A40000A0' ); + + $this->properties['markerWidth'] = 2; + + $this->properties['odometerHeight'] = 0.5; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'borderWidth': + case 'markerWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'borderColor': + case 'startColor': + case 'endColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + case 'odometerHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/pie_chart.php b/src/TUnit/external/ezc/Graph/options/pie_chart.php new file mode 100644 index 0000000..97f109a --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/pie_chart.php @@ -0,0 +1,151 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzRed(); + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->options->label = '%1$s (%3$.1f)'; + * $graph->options->percentThreshold = .05; + * + * $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->render( 400, 150, 'tutorial_pie_chart_options.svg' ); + * + * + * @property string $label + * String used to label pies + * %1$s Name of pie + * %2$d Value of pie + * %3$.1f Percentage + * @property callback $labelCallback + * Callback function to format pie chart labels. + * Function will receive 3 parameters: + * string function( label, value, percent ) + * @property float $sum + * Fixed sum of values. This should be used for incomplete pie + * charts. + * @property float $percentThreshold + * Values with a lower percentage value are aggregated. + * @property float $absoluteThreshold + * Values with a lower absolute value are aggregated. + * @property string $summarizeCaption + * Caption for values summarized because they are lower then the + * configured tresh hold. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphPieChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['label'] = '%1$s: %2$d (%3$.1f%%)'; + $this->properties['labelCallback'] = null; + $this->properties['sum'] = false; + + $this->properties['percentThreshold'] = .0; + $this->properties['absoluteThreshold'] = .0; + $this->properties['summarizeCaption'] = 'Misc'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'label': + $this->properties['label'] = (string) $propertyValue; + break; + case 'labelCallback': + if ( is_callable( $propertyValue ) ) + { + $this->properties['labelCallback'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback function' ); + } + break; + case 'sum': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['sum'] = (float) $propertyValue; + break; + case 'percentThreshold': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['percentThreshold'] = (float) $propertyValue; + break; + case 'absoluteThreshold': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['absoluteThreshold'] = (float) $propertyValue; + break; + case 'summarizeCaption': + $this->properties['summarizeCaption'] = (string) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/radar_chart.php b/src/TUnit/external/ezc/Graph/options/radar_chart.php new file mode 100644 index 0000000..8221f1e --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/radar_chart.php @@ -0,0 +1,172 @@ + + * $wikidata = include 'tutorial_wikipedia_data.php'; + * + * $graph = new ezcGraphRadarChart(); + * $graph->title = 'Wikipedia articles'; + * + * $graph->options->fillLines = 220; + * + * // Add data + * foreach ( $wikidata as $language => $data ) + * { + * $graph->data[$language] = new ezcGraphArrayDataSet( $data ); + * $graph->data[$language][] = reset( $data ); + * } + * + * $graph->render( 400, 150, 'tutorial_radar_chart.svg' ); + * + * + * @property float $lineThickness + * Theickness of chart lines + * @property mixed $fillLines + * Status wheather the space between line and axis should get filled. + * - FALSE to not fill the space at all. + * - (int) Opacity used to fill up the space with the lines color. + * @property int $symbolSize + * Size of symbols in line chart. + * @property ezcGraphFontOptions $highlightFont + * Font configuration for highlight tests + * @property int $highlightSize + * Size of highlight blocks + * @property bool $highlightRadars + * If true, it adds lines to highlight the values position on the + * axis. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphRadarChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lineThickness'] = 1; + $this->properties['fillLines'] = false; + $this->properties['symbolSize'] = 8; + $this->properties['highlightFont'] = new ezcGraphFontOptions(); + $this->properties['highlightFontCloned'] = false; + $this->properties['highlightSize'] = 14; + $this->properties['highlightRadars'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'lineThickness': + case 'symbolSize': + case 'highlightSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'fillLines': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 255 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= int <= 255' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (int) $propertyValue ); + break; + case 'highlightFont': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['highlightFont'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->font; + $this->properties['highlightFontCloned'] = true; + } + + $this->properties['highlightFont']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + $this->properties['highlightSize'] = max( 1, (int) $propertyValue ); + break; + case 'highlightRadars': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['highlightRadars'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'highlightFont': + // Clone font configuration when requested for this element + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->properties['font']; + $this->properties['highlightFontCloned'] = true; + } + return $this->properties['highlightFont']; + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/renderer.php b/src/TUnit/external/ezc/Graph/options/renderer.php new file mode 100644 index 0000000..7f2e6a5 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/renderer.php @@ -0,0 +1,248 @@ + + * $wikidata = include 'tutorial_wikipedia_data.php'; + * + * $graph = new ezcGraphBarChart(); + * $graph->title = 'Wikipedia articles'; + * + * // Add data + * foreach ( $wikidata as $language => $data ) + * { + * $graph->data[$language] = new ezcGraphArrayDataSet( $data ); + * } + * + * // $graph->renderer = new ezcGraphRenderer2d(); + * + * $graph->renderer->options->barMargin = .2; + * $graph->renderer->options->barPadding = .2; + * + * $graph->renderer->options->dataBorder = 0; + * + * $graph->render( 400, 150, 'tutorial_bar_chart_options.svg' ); + * + * + * For additional options, which are special to some chart type you may + * also want to check the option classes for the repective chart type you + * are using and the elements of the chart. The chart type dependant option + * classes are: + * + * - ezcGraphLineChartOptions + * - ezcGraphPieChartOptions + * - ezcGraphRadarChartOptions + * + * There may be additional options dependant on the renderer you are using. + * You may want to check the extensions of this class: + * + * - ezcGraphRenderer2dOptions + * - ezcGraphRenderer3dOptions + * + * @property float $maxLabelHeight + * Percent of chart height used as maximum height for pie chart + * labels. + * @property bool $showSymbol + * Indicates wheather to show the line between pie elements and + * labels. + * @property float $symbolSize + * Size of symbols used to connect a label with a pie. + * @property float $moveOut + * Percent to move pie chart elements out of the middle on highlight. + * @property int $titlePosition + * Position of title in a box. + * @property int $titleAlignement + * Alignement of box titles. + * @property float $dataBorder + * Factor to darken border of data elements, like lines, bars and + * pie segments. + * @property float $barMargin + * Percentual distance between bar blocks. + * @property float $barPadding + * Percentual distance between bars. + * @property float $pieChartOffset + * Offset for starting with first pie chart segment in degrees. + * @property float $legendSymbolGleam + * Opacity of gleam in legend symbols + * @property float $legendSymbolGleamSize + * Size of gleam in legend symbols + * @property float $legendSymbolGleamColor + * Color of gleam in legend symbols + * @property float $pieVerticalSize + * Percent of vertical space used for maximum pie chart size. + * @property float $pieHorizontalSize + * Percent of horizontal space used for maximum pie chart size. + * @property float $pieChartSymbolColor + * Color of pie chart symbols + * @property float $pieChartGleam + * Enhance pie chart with gleam on top. + * @property float $pieChartGleamColor + * Color used for gleam on pie charts. + * @property float $pieChartGleamBorder + * Do not draw gleam on an outer border of this size. + * @property bool $syncAxisFonts + * Synchronize fonts of axis. With the defaut true value, the only + * the fonts of the yAxis will be used. + * @property bool $axisEndStyle + * Style of axis end markers. Defauls to arrow heads, but you may + * also use all symbol constants defined ein the ezcGraph class, + * especially ezcGraph::NO_SYMBOL. + * @property bool $shortAxis + * Defines wheather to render the axis extending the chart boundings + * or stop them at the chart boundings. Deafults to false. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphRendererOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['maxLabelHeight'] = .10; + $this->properties['showSymbol'] = true; + $this->properties['symbolSize'] = 6; + $this->properties['moveOut'] = .1; + $this->properties['titlePosition'] = ezcGraph::TOP; + $this->properties['titleAlignement'] = ezcGraph::MIDDLE | ezcGraph::CENTER; + $this->properties['dataBorder'] = .5; + $this->properties['barMargin'] = .1; + $this->properties['barPadding'] = .05; + $this->properties['pieChartOffset'] = 0; + $this->properties['pieChartSymbolColor'] = ezcGraphColor::fromHex( '#000000' ); + $this->properties['pieChartGleam'] = false; + $this->properties['pieChartGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['pieChartGleamBorder'] = 0; + $this->properties['legendSymbolGleam'] = false; + $this->properties['legendSymbolGleamSize'] = .9; + $this->properties['legendSymbolGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['pieVerticalSize'] = .5; + $this->properties['pieHorizontalSize'] = .25; + $this->properties['syncAxisFonts'] = true; + $this->properties['axisEndStyle'] = ezcGraph::ARROW; + $this->properties['shortAxis'] = false; + + parent::__construct( $options ); + } + + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'dataBorder': + case 'pieChartGleam': + case 'legendSymbolGleam': + if ( $propertyValue !== false && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= float <= 1' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (float) $propertyValue ); + break; + + case 'maxLabelHeight': + case 'moveOut': + case 'barMargin': + case 'barPadding': + case 'legendSymbolGleamSize': + case 'pieVerticalSize': + case 'pieHorizontalSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'symbolSize': + case 'titlePosition': + case 'titleAlignement': + case 'pieChartGleamBorder': + case 'axisEndStyle': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'showSymbol': + case 'syncAxisFonts': + case 'shortAxis': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + $this->properties[$propertyName] = (bool) $propertyValue; + break; + + case 'pieChartOffset': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 360 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 360' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'pieChartSymbolColor': + case 'pieChartGleamColor': + case 'legendSymbolGleamColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/renderer_2d.php b/src/TUnit/external/ezc/Graph/options/renderer_2d.php new file mode 100644 index 0000000..fde2574 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/renderer_2d.php @@ -0,0 +1,120 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteBlack(); + * $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 ezcGraphRenderer2d(); + * + * $graph->renderer->options->moveOut = .2; + * + * $graph->renderer->options->pieChartOffset = 63; + * + * $graph->renderer->options->pieChartGleam = .3; + * $graph->renderer->options->pieChartGleamColor = '#FFFFFF'; + * $graph->renderer->options->pieChartGleamBorder = 2; + * + * $graph->renderer->options->pieChartShadowSize = 3; + * $graph->renderer->options->pieChartShadowColor = '#000000'; + * + * $graph->renderer->options->legendSymbolGleam = .5; + * $graph->renderer->options->legendSymbolGleamSize = .9; + * $graph->renderer->options->legendSymbolGleamColor = '#FFFFFF'; + * + * $graph->renderer->options->pieChartSymbolColor = '#BABDB688'; + * + * $graph->render( 400, 150, 'tutorial_pie_chart_pimped.svg' ); + * + * + * @property int $pieChartShadowSize + * Size of shadows. + * @property float $pieChartShadowTransparency + * Used transparency for pie chart shadows. + * @property float $pieChartShadowColor + * Color used for pie chart shadows. + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphRenderer2dOptions extends ezcGraphRendererOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['pieChartShadowSize'] = 0; + $this->properties['pieChartShadowTransparency'] = .3; + $this->properties['pieChartShadowColor'] = ezcGraphColor::fromHex( '#000000' ); + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'pieChartShadowSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float >= 0' ); + } + + $this->properties['pieChartShadowSize'] = (int) $propertyValue; + break; + case 'pieChartShadowTransparency': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['pieChartShadowTransparency'] = (float) $propertyValue; + break; + case 'pieChartShadowColor': + $this->properties['pieChartShadowColor'] = ezcGraphColor::create( $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/renderer_3d.php b/src/TUnit/external/ezc/Graph/options/renderer_3d.php new file mode 100644 index 0000000..a5834ec --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/renderer_3d.php @@ -0,0 +1,189 @@ + + * $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' ); + * + * + * @property bool $seperateLines + * Indicates wheather the full depth should be used for each line in + * the chart, or beeing seperated by the count of lines. + * @property float $fillAxis + * Transparency used to fill the axis polygon. + * @property float $fillGrid + * Transparency used to fill the grid lines. + * @property float $depth + * Part of picture used to simulate depth of three dimensional chart. + * @property float $pieChartHeight + * Height of the pie charts border. + * @property float $pieChartRotation + * Rotation of pie chart. Defines the percent of width used to + * calculate the height of the ellipse. + * @property int $pieChartShadowSize + * Size of shadows. + * @property float $pieChartShadowTransparency + * Used transparency for pie chart shadows. + * @property float $pieChartShadowColor + * Color used for pie chart shadows. + * @property float $barDarkenSide + * Factor to darken the color used for the bars side polygon. + * @property float $barDarkenTop + * Factor to darken the color used for the bars top polygon. + * @property float $barChartGleam + * Transparancy for gleam on bar charts + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphRenderer3dOptions extends ezcGraphRendererOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['seperateLines'] = true; + $this->properties['fillAxis'] = .8; + $this->properties['fillGrid'] = 0; + $this->properties['depth'] = .1; + $this->properties['pieChartHeight'] = 10.; + $this->properties['pieChartRotation'] = .6; + $this->properties['pieChartShadowSize'] = 0; + $this->properties['pieChartShadowTransparency'] = .3; + $this->properties['pieChartShadowColor'] = ezcGraphColor::fromHex( '#000000' ); + $this->properties['pieChartGleam'] = false; + $this->properties['pieChartGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['barDarkenSide'] = .2; + $this->properties['barDarkenTop'] = .4; + $this->properties['barChartGleam'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'fillAxis': + case 'fillGrid': + if ( $propertyValue !== false && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= float <= 1' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (float) $propertyValue ); + break; + + case 'depth': + case 'pieChartRotation': + case 'pieChartShadowTransparency': + case 'barDarkenSide': + case 'barDarkenTop': + case 'barChartGleam': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'pieChartHeight': + case 'pieChartShadowSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'seperateLines': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['seperateLines'] = $propertyValue; + break; + case 'pieChartShadowColor': + $this->properties['pieChartShadowColor'] = ezcGraphColor::create( $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/options/svg_driver.php b/src/TUnit/external/ezc/Graph/options/svg_driver.php new file mode 100644 index 0000000..45e9a0c --- /dev/null +++ b/src/TUnit/external/ezc/Graph/options/svg_driver.php @@ -0,0 +1,272 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->background->color = '#FFFFFFFF'; + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->driver->options->templateDocument = dirname( __FILE__ ) . '/template.svg'; + * $graph->driver->options->graphOffset = new ezcGraphCoordinate( 25, 40 ); + * $graph->driver->options->insertIntoGroup = 'ezcGraph'; + * + * $graph->render( 400, 200, 'tutorial_driver_svg.svg' ); + * + * + * @property string $encoding + * Encoding of the SVG XML document + * @property float $assumedNumericCharacterWidth + * Assumed percentual average width of chars in numeric strings with + * the used font. + * @property float $assumedTextCharacterWidth + * Assumed percentual average width of chars in non numeric strings + * with the used font. + * @property string $strokeLineCap + * This specifies the shape to be used at the end of open subpaths + * when they are stroked. + * @property string $strokeLineJoin + * This specifies the shape to be used at the edges of paths. + * @property string $shapeRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about what tradeoffs to make as it renders vector + * graphics elements such as 'path' elements and basic shapes such as + * circles and rectangles." + * @property string $colorRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about how to make speed vs. quality tradeoffs as it + * performs color interpolation and compositing. The + * 'color-rendering' property provides a hint to the SVG user agent + * about how to optimize its color interpolation and compositing + * operations." + * @property string $textRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about what tradeoffs to make as it renders text." + * @property mixed $templateDocument + * Use existing SVG document as template to insert graph into. If + * insertIntoGroup is not set, a new group will be inserted in the + * svg root node. + * @property mixed $insertIntoGroup + * ID of a SVG group node to insert the graph. Only works with a + * custom template document. + * @property ezcGraphCoordinate $graphOffset + * Offset of the graph in the svg. + * @property string $idPrefix + * Prefix used for the ids in SVG documents. + * @property string $linkCursor + * CSS value for cursor property used for linked SVG elements + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphSvgDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['encoding'] = null; + $this->properties['assumedNumericCharacterWidth'] = .62; + $this->properties['assumedTextCharacterWidth'] = .53; + $this->properties['strokeLineJoin'] = 'round'; + $this->properties['strokeLineCap'] = 'round'; + $this->properties['shapeRendering'] = 'geometricPrecision'; + $this->properties['colorRendering'] = 'optimizeQuality'; + $this->properties['textRendering'] = 'optimizeLegibility'; + $this->properties['templateDocument'] = false; + $this->properties['insertIntoGroup'] = false; + $this->properties['graphOffset'] = new ezcGraphCoordinate( 0, 0 ); + $this->properties['idPrefix'] = 'ezcGraph'; + $this->properties['linkCursor'] = 'pointer'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'assumedNumericCharacterWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['assumedNumericCharacterWidth'] = (float) $propertyValue; + break; + case 'assumedTextCharacterWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['assumedTextCharacterWidth'] = (float) $propertyValue; + break; + case 'strokeLineJoin': + $values = array( + 'round', + 'miter', + 'bevel', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['strokeLineJoin'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'strokeLineCap': + $values = array( + 'round', + 'butt', + 'square', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['strokeLineCap'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'shapeRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'crispEdges', + 'geometricPrecision', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['shapeRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'colorRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'optimizeQuality', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['colorRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'textRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'optimizeLegibility', + 'geometricPrecision', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['textRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'templateDocument': + if ( !is_file( $propertyValue ) || !is_readable( $propertyValue ) ) + { + throw new ezcBaseFileNotFoundException( $propertyValue ); + } + else + { + $this->properties['templateDocument'] = realpath( $propertyValue ); + } + break; + case 'insertIntoGroup': + if ( !is_string( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'string' ); + } + else + { + $this->properties['insertIntoGroup'] = $propertyValue; + } + break; + case 'graphOffset': + if ( $propertyValue instanceof ezcGraphCoordinate ) + { + $this->properties['graphOffset'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' ); + } + break; + case 'idPrefix': + $this->properties['idPrefix'] = (string) $propertyValue; + break; + case 'encoding': + $this->properties['encoding'] = (string) $propertyValue; + break; + case 'linkCursor': + $this->properties['linkCursor'] = (string) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/palette/black.php b/src/TUnit/external/ezc/Graph/palette/black.php new file mode 100644 index 0000000..86f7fc3 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/black.php @@ -0,0 +1,114 @@ + diff --git a/src/TUnit/external/ezc/Graph/palette/ez.php b/src/TUnit/external/ezc/Graph/palette/ez.php new file mode 100644 index 0000000..2d31a38 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/ez.php @@ -0,0 +1,97 @@ + diff --git a/src/TUnit/external/ezc/Graph/palette/ez_blue.php b/src/TUnit/external/ezc/Graph/palette/ez_blue.php new file mode 100644 index 0000000..ada89f7 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/ez_blue.php @@ -0,0 +1,90 @@ + diff --git a/src/TUnit/external/ezc/Graph/palette/ez_green.php b/src/TUnit/external/ezc/Graph/palette/ez_green.php new file mode 100644 index 0000000..16bd6ef --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/ez_green.php @@ -0,0 +1,90 @@ + diff --git a/src/TUnit/external/ezc/Graph/palette/ez_red.php b/src/TUnit/external/ezc/Graph/palette/ez_red.php new file mode 100644 index 0000000..4d825a0 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/ez_red.php @@ -0,0 +1,90 @@ + diff --git a/src/TUnit/external/ezc/Graph/palette/tango.php b/src/TUnit/external/ezc/Graph/palette/tango.php new file mode 100644 index 0000000..e13f802 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/palette/tango.php @@ -0,0 +1,87 @@ + diff --git a/src/TUnit/external/ezc/Graph/renderer/2d.php b/src/TUnit/external/ezc/Graph/renderer/2d.php new file mode 100644 index 0000000..e1b0951 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/2d.php @@ -0,0 +1,1885 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteBlack(); + * $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 ezcGraphRenderer2d(); + * + * $graph->renderer->options->moveOut = .2; + * + * $graph->renderer->options->pieChartOffset = 63; + * + * $graph->renderer->options->pieChartGleam = .3; + * $graph->renderer->options->pieChartGleamColor = '#FFFFFF'; + * $graph->renderer->options->pieChartGleamBorder = 2; + * + * $graph->renderer->options->pieChartShadowSize = 3; + * $graph->renderer->options->pieChartShadowColor = '#000000'; + * + * $graph->renderer->options->legendSymbolGleam = .5; + * $graph->renderer->options->legendSymbolGleamSize = .9; + * $graph->renderer->options->legendSymbolGleamColor = '#FFFFFF'; + * + * $graph->renderer->options->pieChartSymbolColor = '#BABDB688'; + * + * $graph->render( 400, 150, 'tutorial_pie_chart_pimped.svg' ); + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphRenderer2d + extends + ezcGraphRenderer + implements + ezcGraphRadarRenderer, ezcGraphStackedBarsRenderer, ezcGraphOdometerRenderer +{ + + /** + * 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(); + + /** + * Options + * + * @var ezcGraphRenderer2dOptions + */ + protected $options; + + /** + * Collect axis labels, so that the axis are drawn, when all axis spaces + * are known. + * + * @var array + */ + protected $axisLabels = array(); + + /** + * Collects circle sectors to draw shadow in background of all circle + * sectors. + * + * @var array + */ + protected $circleSectors = array(); + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphRenderer2dOptions( $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 ); + } + } + + /** + * 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->width ) / 2, + $boundings->y0 + ( $boundings->height ) / 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 + ); + + // 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 ) ) + ); + } + + // 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 ), + '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 + ); + + // Split labels up into left an right size and index them on their + // y position + $this->pieSegmentLabels[(int) ($pieSegmentCenter->x > $center->x)][$pieSegmentCenter->y] = array( + new ezcGraphCoordinate( + $center->x + cos( deg2rad( $direction ) ) * $radius * 2 / 3, + $center->y + sin( deg2rad( $direction ) ) * $radius * 2 / 3 + ), + $label, + $context + ); + } + + if ( !$this->pieSegmentBoundings ) + { + $this->pieSegmentBoundings = $boundings; + } + } + + /** + * 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. + * + * @return void + */ + protected function finishCircleSectors() + { + // Add circle sector sides to simple z buffer prioriry list + if ( $this->options->pieChartShadowSize > 0 ) + { + foreach ( $this->circleSectors as $circleSector ) + { + $this->driver->drawCircleSector( + new ezcGraphCoordinate( + $circleSector['center']->x + $this->options->pieChartShadowSize, + $circleSector['center']->y + $this->options->pieChartShadowSize + ), + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $this->options->pieChartShadowColor->transparent( $this->options->pieChartShadowTransparency ), + true + ); + } + } + + foreach ( $this->circleSectors as $circleSector ) + { + // Draw circle sector + $this->addElementReference( + $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $circleSector['color'], + 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 ) + { + $gradient = new ezcGraphLinearGradient( + $circleSector['center'], + new ezcGraphCoordinate( + $circleSector['center']->x, + $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, + $circleSector['start'], + $circleSector['end'], + $gradient, + true + ) + ); + + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $circleSector['height'] / 4 + ), + new ezcGraphCoordinate( + $circleSector['center']->x, + $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, + $circleSector['start'], + $circleSector['end'], + $gradient, + true + ) + ); + } + } + } + + /** + * 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->width ) / 2, + $boundings->y0 + ( $boundings->height ) / 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 ) + { + 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; + } + } + } + + /** + * 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'] + ) + ); + } + } + + /** + * 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; + + $barPointArray = array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + ); + + $this->addElementReference( + $context, + $this->driver->drawPolygon( + $barPointArray, + $color, + true + ) + ); + + if ( $this->options->dataBorder > 0 ) + { + $darkened = $color->darken( $this->options->dataBorder ); + $this->driver->drawPolygon( + $barPointArray, + $darkened, + false, + 1 + ); + } + } + + /** + * 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; + + $barPointArray = array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + ); + + $this->addElementReference( + $context, + $this->driver->drawPolygon( + $barPointArray, + $color, + true + ) + ); + + if ( $this->options->dataBorder > 0 ) + { + $darkened = $color->darken( $this->options->dataBorder ); + $this->driver->drawPolygon( + $barPointArray, + $darkened, + false, + 1 + ); + } + } + + /** + * 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 = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $axisPosition = 0., + $thickness = 1. ) + { + // 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( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + ), + $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( + $start->x + ( $end->x - $start->x ) * $cuttingPosition, + $axisPosition + ); + + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $cuttingPoint->x, + $boundings->y0 + ( $boundings->height ) * $cuttingPoint->y + ), + ), + $fillColor, + true + ); + + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $cuttingPoint->x, + $boundings->y0 + ( $boundings->height ) * $cuttingPoint->y + ), + ), + $fillColor, + true + ); + } + } + + // Draw line + $this->driver->drawLine( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + $color, + $thickness + ); + + // Draw line symbol + if ( $symbol !== ezcGraph::NO_SYMBOL ) + { + if ( $symbolColor === null ) + { + $symbolColor = $color; + } + + $this->linePostSymbols[] = array( + 'boundings' => new ezcGraphBoundings( + $boundings->x0 + ( $boundings->width ) * $end->x - $this->options->symbolSize / 2, + $boundings->y0 + ( $boundings->height ) * $end->y - $this->options->symbolSize / 2, + $boundings->x0 + ( $boundings->width ) * $end->x + $this->options->symbolSize / 2, + $boundings->y0 + ( $boundings->height ) * $end->y + $this->options->symbolSize / 2 + ), + 'color' => $symbolColor, + 'context' => $context, + 'symbol' => $symbol, + ); + } + } + + /** + * Returns a coordinate in the given bounding box for the given angle + * radius with the center as base point. + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphCoordinate $center + * @param float $angle + * @param float $radius + * @return float + */ + protected function getCoordinateFromAngleAndRadius( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $center, + $angle, + $radius + ) + { + $direction = new ezcGraphCoordinate( + sin( $angle ) * $boundings->width / 2, + -cos( $angle ) * $boundings->height / 2 + ); + + $offset = new ezcGraphCoordinate( + sin( $angle ) * $this->xAxisSpace, + -cos( $angle ) * $this->yAxisSpace + ); + + $direction->x -= 2 * $offset->x; + $direction->y -= 2 * $offset->y; + + return new ezcGraphCoordinate( + $boundings->x0 + + $center->x + + $offset->x + + $direction->x * $radius, + $boundings->y0 + + $center->y + + $offset->y + + $direction->y * $radius + ); + } + + /** + * Draw radar chart data line + * + * Draws a line as a data element in a radar chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $center Center of radar chart + * @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 $thickness Line thickness + * @return void + */ + public function drawRadarDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $center, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $thickness = 1. + ) + { + // Calculate line points from chart coordinates + $start = $this->getCoordinateFromAngleAndRadius( + $boundings, + $center, + $start->x * 2 * M_PI, + $start->y + ); + $end = $this->getCoordinateFromAngleAndRadius( + $boundings, + $center, + $end->x * 2 * M_PI, + $end->y + ); + + // Fill line + if ( $fillColor !== null ) + { + $this->driver->drawPolygon( + array( + $start, + $end, + new ezcGraphCoordinate( + $boundings->x0 + $center->x, + $boundings->y0 + $center->y + ), + ), + $fillColor, + true + ); + } + + // Draw line + $this->driver->drawLine( + $start, + $end, + $color, + $thickness + ); + + if ( $symbol !== ezcGraph::NO_SYMBOL ) + { + $this->drawSymbol( + new ezcGraphBoundings( + $end->x - $this->options->symbolSize / 2, + $end->y - $this->options->symbolSize / 2, + $end->x + $this->options->symbolSize / 2, + $end->y + $this->options->symbolSize / 2 + ), + $symbolColor, + $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 ) + { + // Bar specific calculations + if ( $type !== ezcGraph::LINE ) + { + $margin = $stepSize * $this->options->barMargin; + $padding = $stepSize * $this->options->barPadding; + $barWidth = ( $stepSize - $margin ) / $dataCount - $padding; + $offset = -( $dataNumber + ( $dataCount - 1 ) / -2 ) * ( $barWidth + $padding ); + } + + $this->driver->options->font = $font; + $width = $boundings->width / $dataCount; + + $dataPoint = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x + $xOffset + + ( $type === ezcGraph::LINE ? 0 : $offset ), + $boundings->y0 + ( $boundings->height ) * $end->y + $yOffset + ); + + if ( $end->y < $axisPosition ) + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y - $size - $font->padding - $this->options->symbolSize + ), + $width, + $size, + ezcGraph::CENTER | ezcGraph::BOTTOM + ); + } + else + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y + $font->padding + $this->options->symbolSize + ), + $width, + $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->width; + $labelHeight = min( + ( $boundings->height ) / count( $labels ) - $legend->spacing, + $legend->symbolSize + 2 * $legend->padding + ); + } + else + { + $labelWidth = ( $boundings->width ) / 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->width, + $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->width, + $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 ) + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + $boundings->width, + $boundings->height, + $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 ) + { + $this->driver->drawLine( + $start, + $end, + $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 ) + { + $this->driver->drawLine( + $start, + $end, + $color, + 1 + ); + } + + /** + * 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, + ezcGraphBoundings $innerBoundings = null ) + { + // Legacy axis drawing for BC reasons + if ( $innerBoundings === null ) + { + return $this->legacyDrawAxis( $boundings, $start, $end, $axis, $labelClass ); + } + + // Calculate axis start and end points depending on inner boundings + if ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) ) + { + $innerStart = new ezcGraphCoordinate( + $start->x + $boundings->x0, + ( $axis->position === ezcGraph::TOP ? $innerBoundings->y0 : $innerBoundings->y1 ) + ); + $innerEnd = new ezcGraphCoordinate( + $end->x + $boundings->x0, + ( $axis->position === ezcGraph::TOP ? $innerBoundings->y1 : $innerBoundings->y0 ) + ); + } + else + { + $innerStart = new ezcGraphCoordinate( + ( $axis->position === ezcGraph::LEFT ? $innerBoundings->x0 : $innerBoundings->x1 ), + $start->y + $boundings->y0 + ); + $innerEnd = new ezcGraphCoordinate( + ( $axis->position === ezcGraph::LEFT ? $innerBoundings->x1 : $innerBoundings->x0 ), + $end->y + $boundings->y0 + ); + } + + // Shorten axis, if requested + if ( $this->options->shortAxis ) + { + $start = clone $innerStart; + $end = clone $innerEnd; + } + else + { + $start->x += $boundings->x0; + $start->y += $boundings->y0; + $end->x += $boundings->x0; + $end->y += $boundings->y0; + } + + // Determine normalized direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + // Draw axis + $this->driver->drawLine( + $start, + $end, + $axis->border, + 1 + ); + + // Draw small arrowhead + $this->drawAxisArrowHead( + $end, + $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 set + $this->drawAxisLabel( $end, $innerBoundings, $axis ); + + // If font should not be synchronized, use font configuration from + // each axis + if ( $this->options->syncAxisFonts === false ) + { + $this->driver->options->font = $axis->font; + } + + $labelClass->renderLabels( + $this, + $boundings, + $innerStart, + $innerEnd, + $axis, + $innerBoundings + ); + } + + /** + * Draw axis label + * + * Draw labels at the end of an axis. + * + * @param ezcGraphCoordinate $position + * @param ezcGraphBoundings $boundings + * @param ezcGraphChartElementAxis $axis + * @return void + */ + protected function drawAxisLabel( ezcGraphCoordinate $position, ezcGraphBoundings $boundings, ezcGraphChartElementAxis $axis ) + { + if ( $axis->label === false ) + { + return; + } + + $width = $boundings->width; + switch ( $axis->position ) + { + case ezcGraph::TOP: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $position->x + $axis->labelMargin - $width * ( 1 - $axis->axisSpace * 2 ), + $position->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( + $position->x + $axis->labelMargin, + $position->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( + $position->x - $width, + $position->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( + $position->x, + $position->y - $axis->labelSize - $axis->labelMargin + ), + $width - $axis->labelMargin, + $axis->labelSize, + ezcGraph::BOTTOM | ezcGraph::LEFT + ); + break; + } + } + + /** + * 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. + * + * This function is deprecated and will be removed in favor of its + * reimplementation using the innerBoundings parameter. + * + * @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 + * @apichange + * @return void + */ + protected function legacyDrawAxis( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphAxisLabelRenderer $labelClass = null ) + { + // Store axis space for use by label renderer + switch ( $axis->position ) + { + case ezcGraph::TOP: + case ezcGraph::BOTTOM: + $this->xAxisSpace = ( $boundings->width ) * $axis->axisSpace; + break; + case ezcGraph::LEFT: + case ezcGraph::RIGHT: + $this->yAxisSpace = ( $boundings->height ) * $axis->axisSpace; + break; + } + + // Clone boundings because of internal modifications + $boundings = clone $boundings; + + $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; + } + + // Determine normalized direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + // Draw axis + $this->driver->drawLine( + $axisStart, + $axisEnd, + $axis->border, + 1 + ); + + // Draw small arrowhead + $this->drawAxisArrowHead( + $axisEnd, + $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 set + $this->drawAxisLabel( $end, $boundings, $axis ); + + // 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 ) + { + $this->drawAxisLabels(); + } + } + + /** + * Draw all left axis labels + * + * @return void + */ + protected function drawAxisLabels() + { + foreach ( $this->axisLabels as $nr => $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; + } + + $start = $axisLabel['start']; + $end = $axisLabel['end']; + + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Convert elipse to circle for correct angle calculation + $direction->y *= ( $this->xAxisSpace / $this->yAxisSpace ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $movement = new ezcGraphVector( + sin( $angle ) * $this->xAxisSpace * ( $direction->x < 0 ? -1 : 1 ), + cos( $angle ) * $this->yAxisSpace + ); + + $start->x += $movement->x; + $start->y += $movement->y; + $end->x -= $movement->x; + $end->y -= $movement->y; + + $axisLabel['object']->renderLabels( + $this, + $axisLabel['boundings'], + $start, + $end, + $axisLabel['axis'] + ); + + // Prevent from redrawing axis on more then 2 axis. + unset( $this->axisLabels[$nr] ); + } + } + + /** + * 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->width ); + $imageHeight = min( $imageHeight, $boundings->height ); + + $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->width - $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->height - $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->finishCircleSectors(); + $this->finishPieSegmentLabels(); + $this->finishLineSymbols(); + + 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 2D renderer options + $this->pieSegmentLabels = array( + 0 => array(), + 1 => array(), + ); + $this->pieSegmentBoundings = false; + $this->linePostSymbols = array(); + $this->axisLabels = array(); + $this->circleSectors = array(); + } + + /** + * Render odometer chart + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphChartElementAxis $axis + * @param ezcGraphOdometerChartOptions $options + * @return ezcGraphBoundings + */ + public function drawOdometer( + ezcGraphBoundings $boundings, + ezcGraphChartElementAxis $axis, + ezcGraphOdometerChartOptions $options ) + { + $height = $boundings->height * $options->odometerHeight; + + // Draw axis + $oldAxisSpace = $axis->axisSpace; + $axis->axisSpace = 0; + + $axis->render( $this, $boundings ); + + // Reset axisspaces to correct values + $this->xAxisSpace = $boundings->width * $oldAxisSpace; + $this->yAxisSpace = ( $boundings->height - $height ) / 2; + + $this->drawAxisLabels(); + + // Reduce size of chart boundings respecting requested odometer height + $boundings->x0 += $this->xAxisSpace; + $boundings->x1 -= $this->xAxisSpace; + $boundings->y0 += $this->yAxisSpace; + $boundings->y1 -= $this->yAxisSpace; + + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + $options->startColor, + $options->endColor + ); + + // Simply draw box with gradient and optional border + $this->drawBox( + $boundings, + $gradient, + $options->borderColor, + $options->borderWidth + ); + + // Return modified chart boundings + return $boundings; + } + + /** + * Draw a single odometer marker. + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphCoordinate $position + * @param int $symbol + * @param ezcGraphColor $color + * @param int $width + */ + public function drawOdometerMarker( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $position, + $symbol, + ezcGraphColor $color, + $width ) + { + $this->driver->drawLine( + new ezcGraphCoordinate( + $xPos = $boundings->x0 + ( $position->x * $boundings->width ), + $boundings->y0 + ), + new ezcGraphCoordinate( + $xPos, + $boundings->y1 + ), + $color, + $width + ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/3d.php b/src/TUnit/external/ezc/Graph/renderer/3d.php new file mode 100644 index 0000000..a630398 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/3d.php @@ -0,0 +1,2424 @@ + + * $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' ); + * + * + * @version 1.4.3 + * @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(); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_boxed.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_boxed.php new file mode 100644 index 0000000..ac15ebe --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_boxed.php @@ -0,0 +1,218 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisBoxedLabelRenderer(); + * + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisBoxedLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Store step array for later coordinate modifications + * + * @var array(ezcGraphStep) + */ + protected $steps; + + /** + * Store direction for later coordinate modifications + * + * @var ezcGraphVector + */ + protected $direction; + + /** + * Store coordinate width modifier for later coordinate modifications + * + * @var float + */ + protected $widthModifier; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + parent::__construct( $options ); + $this->properties['outerStep'] = true; + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphBoundings $innerBoundings = null ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + $this->steps = $steps; + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $this->direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $this->direction->unify(); + + // Get axis space + $gridBoundings = null; + list( $xSpace, $ySpace ) = $this->getAxisSpace( $renderer, $boundings, $axis, $innerBoundings, $gridBoundings ); + + // Determine additional required axis space by boxes + $firstStep = reset( $steps ); + $lastStep = end( $steps ); + + $this->widthModifier = 1 + $firstStep->width / 2 + $lastStep->width / 2; + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * ( $step->position + $step->width ) / $this->widthModifier, + $start->y + ( $end->y - $start->y ) * ( $step->position + $step->width ) / $this->widthModifier + ); + + $stepWidth = $step->width / $this->widthModifier; + + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $stepWidth, + $axisBoundings->height * $stepWidth + ); + + if ( $this->showLabels ) + { + // Calculate label boundings + switch ( true ) + { + case ( abs( $this->direction->x ) > abs( $this->direction->y ) ) && + ( $this->direction->x > 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $stepSize->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $ySpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + break; + case ( abs( $this->direction->x ) > abs( $this->direction->y ) ) && + ( $this->direction->x < 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $stepSize->x - $this->labelPadding, + $position->y + $ySpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + break; + case ( $this->direction->y > 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $xSpace + $this->labelPadding, + $position->y - $stepSize->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + break; + case ( $this->direction->y < 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $xSpace + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $stepSize->y - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + break; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $this->direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + $firstStep = reset( $this->steps ); + $offset = $firstStep->width / 2 / $this->widthModifier; + + return new ezcGraphCoordinate( + $coordinate->x * abs( $this->direction->y ) + + ( $coordinate->x / $this->widthModifier + $offset ) * abs( $this->direction->x ), + $coordinate->y * abs( $this->direction->x ) + + ( $coordinate->y / $this->widthModifier + $offset ) * abs( $this->direction->y ) + ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_centered.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_centered.php new file mode 100644 index 0000000..2b996fe --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_centered.php @@ -0,0 +1,266 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer(); + * + * + * @property bool $showZeroValue + * Show the value at the zero point of an axis. This value might be + * crossed by the other axis which would result in an unreadable + * label. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisCenteredLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['showZeroValue'] = false; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'showZeroValue': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['showZeroValue'] = (bool) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphBoundings $innerBoundings = null ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Get axis space + $gridBoundings = null; + list( $xSpace, $ySpace ) = $this->getAxisSpace( $renderer, $boundings, $axis, $innerBoundings, $gridBoundings ); + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + if ( ! $step->isZero ) + { + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + + // draw label + if ( $this->showLabels && ( $this->showZeroValue || ! $step->isZero ) ) + { + // Calculate label boundings + if ( abs( $direction->x ) > abs( $direction->y ) ) + { + // Horizontal labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $xSpace * 2, + $step->width * $axisBoundings->width + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $xSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->width, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelSize / 2 + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelSize / 2 - $this->labelPadding, + $position->y + $ySpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + } + else + { + // Vertical labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $ySpace * 2, + $step->width * $axisBoundings->height + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $ySpace * 2, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->height, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $xSpace + $this->labelPadding, + $position->y - $labelSize / 2 + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelSize / 2 - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // Iterate over minor steps + if ( !$step->isLast ) + { + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_exact.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_exact.php new file mode 100644 index 0000000..4421b35 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_exact.php @@ -0,0 +1,304 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisExactLabelRenderer(); + * + * + * @property bool $showLastValue + * Show the last value on the axis, which will be aligned different + * than all other values, to not interfere with the arrow head of + * the axis. + * @property bool $renderLastOutside + * Render the last label outside of the normal axis label boundings + * next to the chart boundings. This may interfere with axis labels + * or cause small font size with a low axisSpace. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisExactLabelRenderer extends ezcGraphAxisLabelRenderer +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['showLastValue'] = true; + $this->properties['renderLastOutside'] = false; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'showLastValue': + case 'renderLastOutside': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties[$propertyName] = (bool) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphBoundings $innerBoundings = null ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Get axis space + $gridBoundings = null; + list( $xSpace, $ySpace ) = $this->getAxisSpace( $renderer, $boundings, $axis, $innerBoundings, $gridBoundings ); + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + if ( ! $step->isZero ) + { + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + + if ( $this->showLabels ) + { + switch ( $axis->position ) + { + case ezcGraph::RIGHT: + case ezcGraph::LEFT: + $labelWidth = $axisBoundings->width * + $steps[$nr - $step->isLast]->width / + ( $this->showLastValue + 1 ); + $labelHeight = $ySpace; + + if ( ( $this->renderLastOutside === true ) && + ( $step->isLast === true ) ) + { + $labelWidth = ( $boundings->width - $axisBoundings->width ) / 2; + } + break; + + case ezcGraph::BOTTOM: + case ezcGraph::TOP: + $labelWidth = $xSpace; + $labelHeight = $axisBoundings->height * + $steps[$nr - $step->isLast]->width / + ( $this->showLastValue + 1 ); + + if ( ( $this->renderLastOutside === true ) && + ( $step->isLast === true ) ) + { + $labelHeight = ( $boundings->height - $axisBoundings->height ) / 2; + } + break; + } + + $showLabel = true; + switch ( true ) + { + case ( !$this->showLastValue && $step->isLast ): + // Skip last step if showLastValue is false + $showLabel = false; + break; + // Draw label at top left of step + case ( ( $axis->position === ezcGraph::BOTTOM ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::BOTTOM ) && + ( $step->isLast ) && + ( $this->renderLastOutside ) ) || + ( ( $axis->position === ezcGraph::TOP ) && + ( $step->isLast ) && + ( !$this->renderLastOutside ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelWidth + $this->labelPadding, + $position->y - $labelHeight + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y - $this->labelPadding + ); + $alignement = ezcGraph::RIGHT | ezcGraph::BOTTOM; + break; + // Draw label at bottom right of step + case ( ( $axis->position === ezcGraph::LEFT ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::LEFT ) && + ( $step->isLast ) && + ( $this->renderLastOutside ) ) || + ( ( $axis->position === ezcGraph::RIGHT ) && + ( $step->isLast ) && + ( !$this->renderLastOutside ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelWidth - $this->labelPadding, + $position->y + $labelHeight - $this->labelPadding + ); + $alignement = ezcGraph::LEFT | ezcGraph::TOP; + break; + // Draw label at bottom left of step + case ( ( $axis->position === ezcGraph::TOP ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::TOP ) && + ( $step->isLast ) && + ( $this->renderLastOutside ) ) || + ( ( $axis->position === ezcGraph::RIGHT ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::RIGHT ) && + ( $step->isLast ) && + ( $this->renderLastOutside ) ) || + ( ( $axis->position === ezcGraph::BOTTOM ) && + ( $step->isLast ) && + ( !$this->renderLastOutside ) ) || + ( ( $axis->position === ezcGraph::LEFT ) && + ( $step->isLast ) && + ( !$this->renderLastOutside ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelWidth + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelHeight - $this->labelPadding + ); + $alignement = ezcGraph::RIGHT | ezcGraph::TOP; + break; + } + + if ( $showLabel ) + { + $renderer->drawText( + $labelBoundings, + $step->label, + $alignement + ); + } + } + + if ( !$step->isLast ) + { + // Iterate over minor steps + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_none.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_none.php new file mode 100644 index 0000000..120a554 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_none.php @@ -0,0 +1,44 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer(); + * + * + * @version 1.4.3 + * @package Graph + */ +class ezcGraphAxisNoLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + return true; + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_radar.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_radar.php new file mode 100644 index 0000000..f7d28b9 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_radar.php @@ -0,0 +1,322 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer(); + * + * + * @property float $lastStep + * Position of last step on the axis to calculate the grid. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisRadarLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lastStep'] = null; + + parent::__construct( $options ); + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'lastStep': + if ( !is_null( $propertyValue ) && + ( !is_float( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['lastStep'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + // Draw major grid + if ( ( $this->lastStep !== null ) && $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $boundings, + $position, + $stepSize, + $axis->majorGrid, + $step->position + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + + // draw label + if ( $this->showLabels && ( $this->lastStep === null ) ) + { + // Calculate label boundings + if ( abs( $direction->x ) > abs( $direction->y ) ) + { + // Horizontal labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->xAxisSpace * 2, + $step->width * $axisBoundings->width + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->xAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->width, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelSize / 2 + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelSize / 2 - $this->labelPadding, + $position->y + $renderer->yAxisSpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + } + else + { + // Vertical labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->yAxisSpace * 2, + $step->width * $axisBoundings->height + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->yAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->height, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $renderer->xAxisSpace + $this->labelPadding, + $position->y - $labelSize / 2 + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelSize / 2 - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // Iterate over minor steps + if ( !$step->isLast ) + { + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( ( $this->lastStep !== null ) && $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $boundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid, + $minorStep->position + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } + + /** + * Draw grid + * + * Draws a grid line at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @param int $stepPosition + * @return void + */ + protected function drawGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color, $stepPosition = null ) + { + // Calculate position on last axis + $start = new ezcGraphCoordinate( + $boundings->x0 + $width = ( $boundings->width / 2 ), + $boundings->y0 + $height = ( $boundings->height / 2 ) + ); + + $lastAngle = $this->lastStep * 2 * M_PI; + $end = new ezcGraphCoordinate( + $start->x + sin( $lastAngle ) * $width, + $start->y - cos( $lastAngle ) * $height + ); + + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Convert elipse to circle for correct angle calculation + $direction->y *= ( $renderer->xAxisSpace / $renderer->yAxisSpace ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $movement = new ezcGraphVector( + sin( $angle ) * $renderer->xAxisSpace + * ( $direction->x < 0 ? -1 : 1 ), + cos( $angle ) * $renderer->yAxisSpace + ); + + $start->x += $movement->x; + $start->y += $movement->y; + $end->x -= $movement->x; + $end->y -= $movement->y; + + $lastPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $stepPosition, + $start->y + ( $end->y - $start->y ) * $stepPosition + ); + + $renderer->drawGridLine( + $position, + $lastPosition, + $color + ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/renderer/axis_label_rotated.php b/src/TUnit/external/ezc/Graph/renderer/axis_label_rotated.php new file mode 100644 index 0000000..a8f5d9a --- /dev/null +++ b/src/TUnit/external/ezc/Graph/renderer/axis_label_rotated.php @@ -0,0 +1,433 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisRotatedLabelRenderer(); + * + * // Define angle manually in degree + * $chart->xAxis->axisLabelRenderer->angle = 45; + * + * // Increase axis space + * $chart->xAxis->axisSpace = .2; + * + * + * @property float $angle + * Angle of labels on axis in degrees. + * + * @version 1.4.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisRotatedLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Store step array for later coordinate modifications + * + * @var array(ezcGraphStep) + */ + protected $steps; + + /** + * Store direction for later coordinate modifications + * + * @var ezcGraphVector + */ + protected $direction; + + /** + * Store coordinate width modifier for later coordinate modifications + * + * @var float + */ + protected $widthModifier; + + /** + * Store coordinate offset for later coordinate modifications + * + * @var float + */ + protected $offset; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + parent::__construct( $options ); + $this->properties['angle'] = null; + $this->properties['labelOffset'] = true; + } + + /** + * __set + * + * @param mixed $propertyName + * @param mixed $propertyValue + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + * @throws ezcBasePropertyNotFoundException + * If a the value for the property options is not an instance of + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'angle': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float < 360' ); + } + + $reducement = (int) ( $propertyValue - $propertyValue % 360 ); + $this->properties['angle'] = (float) $propertyValue - $reducement; + break; + + case 'labelOffset': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties[$propertyName] = (bool) $propertyValue; + break; + + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphBoundings $innerBoundings = null ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + $this->steps = $steps; + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $this->direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $this->direction->unify(); + $axisAngle = -$this->direction->angle( new ezcGraphVector( 1, 0 ) ); + + // Get axis space + $gridBoundings = null; + list( $xSpace, $ySpace ) = $this->getAxisSpace( $renderer, $boundings, $axis, $innerBoundings, $gridBoundings ); + + // Determine optimal angle if none specified + if ( $this->angle === null ) + { + $minimumStepWidth = null; + foreach ( $steps as $nr => $step ) + { + if ( ( $minimumStepWidth === null ) || + ( $step->width < $minimumStepWidth ) ) + { + $minimumStepWidth = $step->width; + } + } + + $width = abs( + $axisBoundings->width * $minimumStepWidth * $this->direction->x + + $axisBoundings->height * $minimumStepWidth * $this->direction->y + ); + $height = abs( + $ySpace * $this->direction->x + + $xSpace * $this->direction->y + ); + + $length = sqrt( pow( $width, 2 ) + pow( $height, 2 ) ); + $this->angle = rad2deg( acos( $height / $length ) ); + } + + // Determine additional required axis space by boxes + $firstStep = reset( $steps ); + $lastStep = end( $steps ); + + $textAngle = $axisAngle + + deg2rad( $this->angle ) + + ( $axis->position & ( ezcGraph::TOP | ezcGraph::BOTTOM ) ? deg2rad( 270 ) : deg2rad( 90 ) ); + + // Ensure angle between 0 and 360 degrees + $degTextAngle = rad2deg( $textAngle ); + while ( $degTextAngle < 0 ) + { + $degTextAngle += 360.; + } + + if ( $this->properties['labelOffset'] ) + { + $this->offset = + ( $this->angle < 0 ? -1 : 1 ) * + ( $axis->position & ( ezcGraph::TOP | ezcGraph::LEFT ) ? 1 : -1 ) * + ( 1 - cos( deg2rad( $this->angle * 2 ) ) ); + } + else + { + $this->offset = 0; + } + + $axisSpaceFactor = abs( + ( $this->direction->x == 0 ? 0 : + $this->direction->x * $ySpace / $axisBoundings->width ) + + ( $this->direction->y == 0 ? 0 : + $this->direction->y * $xSpace / $axisBoundings->height ) + ); + + $start = new ezcGraphCoordinate( + $start->x + max( 0., $axisSpaceFactor * $this->offset ) * ( $end->x - $start->x ), + $start->y + max( 0., $axisSpaceFactor * $this->offset ) * ( $end->y - $start->y ) + ); + $end = new ezcGraphCoordinate( + $end->x + min( 0., $axisSpaceFactor * $this->offset ) * ( $end->x - $start->x ), + $end->y + min( 0., $axisSpaceFactor * $this->offset ) * ( $end->y - $start->y ) + ); + + $labelLength = sqrt( + pow( + $xSpace * $this->direction->y + + $axisSpaceFactor * $this->offset * ( $end->x - $start->x ), + 2 ) + + pow( + $ySpace * $this->direction->x + + $axisSpaceFactor * $this->offset * ( $end->y - $start->y ), + 2 ) + ); + + $this->offset *= $axisSpaceFactor; + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position * abs( $this->direction->x ), + $start->y + ( $end->y - $start->y ) * $step->position * abs( $this->direction->y ) + ); + + $stepSize = new ezcGraphCoordinate( + ( $end->x - $start->x ) * $step->width, + ( $end->y - $start->y ) * $step->width + ); + + // Calculate label boundings + switch ( true ) + { + case ( $nr === 0 ): + $labelSize = min( + abs( + $xSpace * 2 * $this->direction->y + + $ySpace * 2 * $this->direction->x ), + abs( + $step->width * $axisBoundings->width * $this->direction->x + + $step->width * $axisBoundings->height * $this->direction->y ) + ); + break; + case ( $step->isLast ): + $labelSize = min( + abs( + $xSpace * 2 * $this->direction->y + + $ySpace * 2 * $this->direction->x ), + abs( + $steps[$nr - 1]->width * $axisBoundings->width * $this->direction->x + + $steps[$nr - 1]->width * $axisBoundings->height * $this->direction->y ) + ); + break; + default: + $labelSize = abs( + $step->width * $axisBoundings->width * $this->direction->x + + $step->width * $axisBoundings->height * $this->direction->y + ); + break; + } + + $labelSize = $labelSize * cos( deg2rad( $this->angle ) ); + $lengthReducement = min( + abs( tan( deg2rad( $this->angle ) ) * ( $labelSize / 2 ) ), + abs( $labelLength / 2 ) + ); + + switch ( true ) + { + case ( ( ( $degTextAngle >= 0 ) && + ( $degTextAngle < 90 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 270 ) && + ( $degTextAngle < 360 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x, + $position->y, + $position->x + abs( $labelLength ) - $lengthReducement, + $position->y + $labelSize + ); + $labelAlignement = ezcGraph::LEFT | ezcGraph::TOP; + $labelRotation = $degTextAngle; + break; + case ( ( ( $degTextAngle >= 90 ) && + ( $degTextAngle < 180 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 180 ) && + ( $degTextAngle < 270 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x - abs( $labelLength ) + $lengthReducement, + $position->y, + $position->x, + $position->y + $labelSize + ); + $labelAlignement = ezcGraph::RIGHT | ezcGraph::TOP; + $labelRotation = $degTextAngle - 180; + break; + case ( ( ( $degTextAngle >= 180 ) && + ( $degTextAngle < 270 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 90 ) && + ( $degTextAngle < 180 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x - abs( $labelLength ) + $lengthReducement, + $position->y - $labelSize, + $position->x, + $position->y + ); + $labelAlignement = ezcGraph::RIGHT | ezcGraph::BOTTOM; + $labelRotation = $degTextAngle - 180; + break; + case ( ( ( $degTextAngle >= 270 ) && + ( $degTextAngle < 360 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 0 ) && + ( $degTextAngle < 90 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x, + $position->y + $labelSize, + $position->x + abs( $labelLength ) - $lengthReducement, + $position->y + ); + $labelAlignement = ezcGraph::LEFT | ezcGraph::BOTTOM; + $labelRotation = $degTextAngle; + break; + } + + $renderer->drawText( + $labelBoundings, + $step->label, + $labelAlignement, + new ezcGraphRotation( + $labelRotation, + $position + ) + ); + + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $this->direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + return new ezcGraphCoordinate( + $coordinate->x * abs( $this->direction->y ) + + ( $coordinate->x * ( 1 - abs( $this->offset ) ) + max( 0, $this->offset ) ) * abs( $this->direction->x ), + $coordinate->y * abs( $this->direction->x ) + + ( $coordinate->y * ( 1 - abs( $this->offset ) ) + max( 0, $this->offset ) ) * abs( $this->direction->y ) + ); + } +} +?> diff --git a/src/TUnit/external/ezc/Graph/structs/context.php b/src/TUnit/external/ezc/Graph/structs/context.php new file mode 100644 index 0000000..ab3fa72 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/structs/context.php @@ -0,0 +1,92 @@ +dataset = $dataset; + $this->datapoint = $datapoint; + $this->url = $url; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->dataset = (string) $properties['dataset']; + $this->datapoint = (string) $properties['datapoint']; + + // Check to keep BC + // @TODO: Remvove unnesecary check on next major version + if ( array_key_exists( 'url', $properties ) ) + { + $this->url = (string) $properties['url']; + } + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/structs/coordinate.php b/src/TUnit/external/ezc/Graph/structs/coordinate.php new file mode 100644 index 0000000..0af65b7 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/structs/coordinate.php @@ -0,0 +1,76 @@ +x = $x; + $this->y = $y; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->x = $properties['x']; + $this->y = $properties['y']; + } + + /** + * Returns simple string representation of coordinate + * + * @return string + * @ignore + */ + public function __toString() + { + return sprintf( '( %.2f, %.2f )', $this->x, $this->y ); + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/structs/step.php b/src/TUnit/external/ezc/Graph/structs/step.php new file mode 100644 index 0000000..546e375 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/structs/step.php @@ -0,0 +1,106 @@ +position = (float) $position; + $this->width = (float) $width; + $this->label = $label; + $this->childs = $childs; + $this->isZero = (bool) $isZero; + $this->isLast = (bool) $isLast; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->position = $properties['position']; + $this->width = $properties['width']; + $this->label = $properties['label']; + $this->childs = $properties['childs']; + $this->isZero = $properties['isZero']; + $this->isLast = $properties['isLast']; + } +} + +?> diff --git a/src/TUnit/external/ezc/Graph/tools.php b/src/TUnit/external/ezc/Graph/tools.php new file mode 100644 index 0000000..8c34385 --- /dev/null +++ b/src/TUnit/external/ezc/Graph/tools.php @@ -0,0 +1,183 @@ +driver instanceof ezcGraphGdDriver ) ) + { + throw new ezcGraphToolsIncompatibleDriverException( $chart->driver, 'ezcGraphGdDriver' ); + } + + $elements = $chart->renderer->getElementReferences(); + + if ( !count( $elements ) ) + { + throw new ezcGraphToolsNotRenderedException( $chart ); + } + + $imageMap = sprintf( "\n", $name ); + + // Iterate over legends elements + if ( isset( $elements['legend'] ) ) + { + foreach ( $elements['legend'] as $objectName => $polygones ) + { + $url = $elements['legend_url'][$objectName]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $polygones as $shape => $polygone ) + { + $coordinateString = ''; + foreach ( $polygone as $coordinate ) + { + $coordinateString .= sprintf( '%d,%d,', $coordinate->x, $coordinate->y ); + } + + $imageMap .= sprintf( "\t\"%s\"\n", + substr( $coordinateString, 0, -1 ), + $url, + $objectName + ); + } + } + } + + // Iterate over data + foreach ( $elements['data'] as $dataset => $datapoints ) + { + foreach ( $datapoints as $datapoint => $polygones ) + { + $url = $chart->data[$dataset]->url[$datapoint]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $polygones as $polygon ) + { + $coordinateString = ''; + foreach ( $polygon as $coordinate ) + { + $coordinateString .= sprintf( '%d,%d,', $coordinate->x, $coordinate->y ); + } + + $imageMap .= sprintf( "\t\"%s\"\n", + substr( $coordinateString, 0, -1 ), + $url, + $datapoint + ); + } + } + } + + return $imageMap . "\n"; + } + + /** + * Add links to clickable SVG elements in a chart with SVG driver. + * + * @param ezcGraphChart $chart + * @return void + */ + public static function linkSvgElements( ezcGraphChart $chart ) + { + if ( ! ( $chart->driver instanceof ezcGraphSvgDriver ) ) + { + throw new ezcGraphToolsIncompatibleDriverException( $chart->driver, 'ezcGraphSvgDriver' ); + } + + $fileName = $chart->getRenderedFile(); + + if ( !$fileName ) + { + throw new ezcGraphToolsNotRenderedException( $chart ); + } + + $dom = new DOMDocument(); + $dom->load( $fileName ); + $xpath = new DomXPath( $dom ); + + $elements = $chart->renderer->getElementReferences(); + + // Link chart elements + foreach ( $elements['data'] as $dataset => $datapoints ) + { + foreach ( $datapoints as $datapoint => $ids ) + { + $url = $chart->data[$dataset]->url[$datapoint]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $ids as $id ) + { + $element = $xpath->query( '//*[@id = \'' . $id . '\']' )->item( 0 ); + + $element->setAttribute( 'style', $element->getAttribute( 'style' ) . ' cursor: ' . $chart->driver->options->linkCursor . ';' ); + $element->setAttribute( 'onclick', "top.location = '{$url}'" ); + } + } + } + + // Link legend elements + if ( isset( $elements['legend'] ) ) + { + foreach ( $elements['legend'] as $objectName => $ids ) + { + $url = $elements['legend_url'][$objectName]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $ids as $id ) + { + $element = $xpath->query( '//*[@id = \'' . $id . '\']' )->item( 0 ); + + $element->setAttribute( 'style', $element->getAttribute( 'style' ) . ' cursor: ' . $chart->driver->options->linkCursor . ';' ); + $element->setAttribute( 'onclick', "top.location = '{$url}'" ); + } + } + } + + $dom->save( $fileName ); + } +} + +?> diff --git a/src/TUnit/framework/reporting/template/style.css b/src/TUnit/framework/reporting/template/style.css index f5a6c45..2c538eb 100644 --- a/src/TUnit/framework/reporting/template/style.css +++ b/src/TUnit/framework/reporting/template/style.css @@ -2,7 +2,7 @@ html { overflow: auto; } body { - background-color: #CCFFCC; + background-color: #EEEEEE; color: #000000; font-family: Consolas, "Courier New", monospace; font-size: 16px; diff --git a/src/TUnit/manifest.php b/src/TUnit/manifest.php index 6dbf1eb..4734366 100644 --- a/src/TUnit/manifest.php +++ b/src/TUnit/manifest.php @@ -3,11 +3,11 @@ /** * Autoload manifest * - * Autogenerated by manifester.php on 2009-06-27 18:04:06 + * Autogenerated by manifester.php on 2009-06-30 01:05:55 * * @package TUnit * @version 0.5.0 - * @since 0.5.0 + * @since 1.0 */ return array( diff --git a/tools/manifester.php b/tools/manifester.php index 2188ba4..7309577 100644 --- a/tools/manifester.php +++ b/tools/manifester.php @@ -37,13 +37,14 @@ global $switches; $switches = new CliSwitchCollection(); - $switches->addSwitch(new CliSwitch('directory', 'd', true, 'dir1,dir2,...', 'Comma-delimited list of directories')) - ->addSwitch(new CliSwitch('version', 'v', true, 'version_number', 'Version number for use in @version tag')) - ->addSwitch(new CliSwitch('package', 'p', true, 'package_name ', 'Name of the package for use in @package tag')) - ->addSwitch(new CliSwitch('output', 'o', false, 'file', 'Name of the output file, defaults to stdout if empty')) - ->addSwitch(new CliSwitch('quiet', 'q', false, null, 'Do not print progress messages')) - ->addSwitch(new CliSwitch('recursive', 'r', false, null, 'Recursively walk the directories')) - ->addSwitch(new CliSwitch('base-dir', 'b', true, 'dir', 'Base directory')); + $switches->addSwitch(new CliSwitch('directory', 'd', true, 'dir1,dir2,...', 'Comma-delimited list of directories')) + ->addSwitch(new CliSwitch('version', 'v', true, 'version_number', 'Version number for use in @version tag')) + ->addSwitch(new CliSwitch('package', 'p', true, 'package_name ', 'Name of the package for use in @package tag')) + ->addSwitch(new CliSwitch('output', 'o', false, 'file', 'Name of the output file, defaults to stdout if empty')) + ->addSwitch(new CliSwitch('quiet', 'q', false, null, 'Do not print progress messages')) + ->addSwitch(new CliSwitch('recursive', 'r', false, null, 'Recursively walk the directories')) + ->addSwitch(new CliSwitch('ignore', 'i', false, 'pattern1,pattern2...', 'File patterns to ignore')) + ->addSwitch(new CliSwitch('base-dir', 'b', true, 'dir', 'Base directory')); array_shift($argv); $args = Cli::parseArgs($argv, $switches); @@ -77,7 +78,7 @@ * * @package $args[package] * @version $args[version] - * @since $args[version] + * @since 1.0 */ return array( @@ -87,6 +88,11 @@ ENDDATA; $classes = array(); $maxClassNameLength = 0; + $ignore = array(DIRECTORY_SEPARATOR . '.'); + if (isset($args['ignore']) && !empty($args['ignore'])) { + $ignore = array_merge($ignore, explode(',', $args['ignore'])); + } + foreach ($dirs as $dir) { if (!is_dir($dir)) { fwrite(STDERR, $dir . ' is not a directory... skipping'); @@ -96,7 +102,12 @@ ENDDATA; $iterator = $args['recursive'] ? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) : new DirectoryIterator($dir); foreach ($iterator as $file) { - if ($file->isFile() && strpos($file->getPathName(), DIRECTORY_SEPARATOR . '.') === false && substr($file->getFileName(), -4) === '.php') { + if ( + $file->isFile() && + strpos($file->getPathName(), DIRECTORY_SEPARATOR . '.') === false && + substr($file->getFileName(), -4) === '.php' && + array_reduce($ignore, create_function('$old, $new', 'return $old && strpos(\'' . addslashes($file->getPathName()) . '\', $new) === false;'), true) + ) { if (!$args['quiet']) { echo 'Processing ' . $file->getPathName() . "\n"; }