diff --git a/check.php b/check.php new file mode 100644 index 0000000..b63e606 --- /dev/null +++ b/check.php @@ -0,0 +1,106 @@ + str_replace(APP_PATH, "", $sf), 'md5' => md5_file($sf)); + } + } + self::message(0, '', $files); + } + + //下载文件 + static function download() + { + global $_GPC; + $entry = APP_PATH . trim($_GPC['path']); + if (is_file($entry) && self::files_filter($entry)) { + $content = file_get_contents($entry); + self::message(0, '', array('path' => $_GPC['path'], 'content' => base64_encode($content))); + } + self::message(1, '路径错误,文件不存在'); + } + + //移除无需更新文件 + static function files_filter($file) + { + $file_type = [ + '.log', + '.txt', + '.zip', + 'check.php', + 'install.php', + '.md', + 'LICENSE', + APP_PATH . 'addons/weliam_smartcity/data/', + 'manifest.xml', + APP_PATH . 'addons/weliam_smartcity/plugin/weliam_house/house/runtime/', + '/attachment/',//临时附件文件 + '/data/tpl',//页面模板缓存文件 + '/data/log',//日志文件 + ]; + foreach ($file_type as $value) { + if (strpos($file, $value) !== FALSE) { + return FALSE; + } + } + return TRUE; + } + + //根据路径返回当前路径下所有文件 + static function files_tree($path) + { + $files = array(); + $ds = glob($path . '/*'); + if (is_array($ds)) { + foreach ($ds as $entry) { + if (is_file($entry)) { + $files[] = $entry; + } + if (is_dir($entry)) { + $rs = self::files_tree($entry); + foreach ($rs as $f) { + $files[] = $f; + } + } + } + } + return $files; + } + + //调试函数 + static function wl_debug($array = array()) + { + echo "
";
+ print_r($array);
+ exit();
+ }
+
+ //数据输出函数
+ static function message($code = 0, $message = array(), $data = array())
+ {
+ die(json_encode(array('code' => $code, 'message' => $message, 'data' => $data)));
+ }
+}
+
diff --git a/framework/library/phpexcel/PHPExcel/CachedObjectStorageFactory.php b/framework/library/phpexcel/PHPExcel/CachedObjectStorageFactory.php
new file mode 100644
index 0000000..8d394fe
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/CachedObjectStorageFactory.php
@@ -0,0 +1,251 @@
+ array(
+ ),
+ self::cache_in_memory_gzip => array(
+ ),
+ self::cache_in_memory_serialized => array(
+ ),
+ self::cache_igbinary => array(
+ ),
+ self::cache_to_phpTemp => array( 'memoryCacheSize' => '1MB'
+ ),
+ self::cache_to_discISAM => array( 'dir' => NULL
+ ),
+ self::cache_to_apc => array( 'cacheTime' => 600
+ ),
+ self::cache_to_memcache => array( 'memcacheServer' => 'localhost',
+ 'memcachePort' => 11211,
+ 'cacheTime' => 600
+ ),
+ self::cache_to_wincache => array( 'cacheTime' => 600
+ ),
+ self::cache_to_sqlite => array(
+ ),
+ self::cache_to_sqlite3 => array(
+ ),
+ );
+
+
+ /**
+ * Arguments for the active cache storage method
+ *
+ * @var array of mixed array
+ */
+ private static $_storageMethodParameters = array();
+
+
+ /**
+ * Return the current cache storage method
+ *
+ * @return string|NULL
+ **/
+ public static function getCacheStorageMethod()
+ {
+ return self::$_cacheStorageMethod;
+ } // function getCacheStorageMethod()
+
+
+ /**
+ * Return the current cache storage class
+ *
+ * @return PHPExcel_CachedObjectStorage_ICache|NULL
+ **/
+ public static function getCacheStorageClass()
+ {
+ return self::$_cacheStorageClass;
+ } // function getCacheStorageClass()
+
+
+ /**
+ * Return the list of all possible cache storage methods
+ *
+ * @return string[]
+ **/
+ public static function getAllCacheStorageMethods()
+ {
+ return self::$_storageMethods;
+ } // function getCacheStorageMethods()
+
+
+ /**
+ * Return the list of all available cache storage methods
+ *
+ * @return string[]
+ **/
+ public static function getCacheStorageMethods()
+ {
+ $activeMethods = array();
+ foreach(self::$_storageMethods as $storageMethod) {
+ $cacheStorageClass = 'PHPExcel_CachedObjectStorage_' . $storageMethod;
+ if (call_user_func(array($cacheStorageClass, 'cacheMethodIsAvailable'))) {
+ $activeMethods[] = $storageMethod;
+ }
+ }
+ return $activeMethods;
+ } // function getCacheStorageMethods()
+
+
+ /**
+ * Identify the cache storage method to use
+ *
+ * @param string $method Name of the method to use for cell cacheing
+ * @param array of mixed $arguments Additional arguments to pass to the cell caching class
+ * when instantiating
+ * @return boolean
+ **/
+ public static function initialize($method = self::cache_in_memory, $arguments = array())
+ {
+ if (!in_array($method,self::$_storageMethods)) {
+ return FALSE;
+ }
+
+ $cacheStorageClass = 'PHPExcel_CachedObjectStorage_'.$method;
+ if (!call_user_func(array( $cacheStorageClass,
+ 'cacheMethodIsAvailable'))) {
+ return FALSE;
+ }
+
+ self::$_storageMethodParameters[$method] = self::$_storageMethodDefaultParameters[$method];
+ foreach($arguments as $k => $v) {
+ if (array_key_exists($k, self::$_storageMethodParameters[$method])) {
+ self::$_storageMethodParameters[$method][$k] = $v;
+ }
+ }
+
+ if (self::$_cacheStorageMethod === NULL) {
+ self::$_cacheStorageClass = 'PHPExcel_CachedObjectStorage_' . $method;
+ self::$_cacheStorageMethod = $method;
+ }
+ return TRUE;
+ } // function initialize()
+
+
+ /**
+ * Initialise the cache storage
+ *
+ * @param PHPExcel_Worksheet $parent Enable cell caching for this worksheet
+ * @return PHPExcel_CachedObjectStorage_ICache
+ **/
+ public static function getInstance(PHPExcel_Worksheet $parent)
+ {
+ $cacheMethodIsAvailable = TRUE;
+ if (self::$_cacheStorageMethod === NULL) {
+ $cacheMethodIsAvailable = self::initialize();
+ }
+
+ if ($cacheMethodIsAvailable) {
+ $instance = new self::$_cacheStorageClass( $parent,
+ self::$_storageMethodParameters[self::$_cacheStorageMethod]
+ );
+ if ($instance !== NULL) {
+ return $instance;
+ }
+ }
+
+ return FALSE;
+ } // function getInstance()
+
+
+ /**
+ * Clear the cache storage
+ *
+ **/
+ public static function finalize()
+ {
+ self::$_cacheStorageMethod = NULL;
+ self::$_cacheStorageClass = NULL;
+ self::$_storageMethodParameters = array();
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Calculation.php b/framework/library/phpexcel/PHPExcel/Calculation.php
new file mode 100644
index 0000000..4345ee5
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Calculation.php
@@ -0,0 +1,3891 @@
+=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})');
+ // Named Range of cells
+ define('CALCULATION_REGEXP_NAMEDRANGE','((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)');
+ } else {
+ // Cell reference (cell or range of cells, with or without a sheet reference)
+ define('CALCULATION_REGEXP_CELLREF','(((\w*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d+)');
+ // Named Range of cells
+ define('CALCULATION_REGEXP_NAMEDRANGE','(((\w*)|(\'.*\')|(\".*\"))!)?([_A-Z][_A-Z0-9\.]*)');
+ }
+}
+
+
+/**
+ * PHPExcel_Calculation (Multiton)
+ *
+ * @category PHPExcel
+ * @package PHPExcel_Calculation
+ * @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
+ */
+class PHPExcel_Calculation {
+
+ /** Constants */
+ /** Regular Expressions */
+ // Numeric operand
+ const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
+ // String operand
+ const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
+ // Opening bracket
+ const CALCULATION_REGEXP_OPENBRACE = '\(';
+ // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
+ const CALCULATION_REGEXP_FUNCTION = '@?([A-Z][A-Z0-9\.]*)[\s]*\(';
+ // Cell reference (cell or range of cells, with or without a sheet reference)
+ const CALCULATION_REGEXP_CELLREF = CALCULATION_REGEXP_CELLREF;
+ // Named Range of cells
+ const CALCULATION_REGEXP_NAMEDRANGE = CALCULATION_REGEXP_NAMEDRANGE;
+ // Error
+ const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
+
+
+ /** constants */
+ const RETURN_ARRAY_AS_ERROR = 'error';
+ const RETURN_ARRAY_AS_VALUE = 'value';
+ const RETURN_ARRAY_AS_ARRAY = 'array';
+
+ private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
+
+
+ /**
+ * Instance of this class
+ *
+ * @access private
+ * @var PHPExcel_Calculation
+ */
+ private static $_instance;
+
+
+ /**
+ * Instance of the workbook this Calculation Engine is using
+ *
+ * @access private
+ * @var PHPExcel
+ */
+ private $_workbook;
+
+ /**
+ * List of instances of the calculation engine that we've instantiated for individual workbooks
+ *
+ * @access private
+ * @var PHPExcel_Calculation[]
+ */
+ private static $_workbookSets;
+
+ /**
+ * Calculation cache
+ *
+ * @access private
+ * @var array
+ */
+ private $_calculationCache = array ();
+
+
+ /**
+ * Calculation cache enabled
+ *
+ * @access private
+ * @var boolean
+ */
+ private $_calculationCacheEnabled = TRUE;
+
+
+ /**
+ * List of operators that can be used within formulae
+ * The true/false value indicates whether it is a binary operator or a unary operator
+ *
+ * @access private
+ * @var array
+ */
+ private static $_operators = array('+' => TRUE, '-' => TRUE, '*' => TRUE, '/' => TRUE,
+ '^' => TRUE, '&' => TRUE, '%' => FALSE, '~' => FALSE,
+ '>' => TRUE, '<' => TRUE, '=' => TRUE, '>=' => TRUE,
+ '<=' => TRUE, '<>' => TRUE, '|' => TRUE, ':' => TRUE
+ );
+
+
+ /**
+ * List of binary operators (those that expect two operands)
+ *
+ * @access private
+ * @var array
+ */
+ private static $_binaryOperators = array('+' => TRUE, '-' => TRUE, '*' => TRUE, '/' => TRUE,
+ '^' => TRUE, '&' => TRUE, '>' => TRUE, '<' => TRUE,
+ '=' => TRUE, '>=' => TRUE, '<=' => TRUE, '<>' => TRUE,
+ '|' => TRUE, ':' => TRUE
+ );
+
+ /**
+ * The debug log generated by the calculation engine
+ *
+ * @access private
+ * @var PHPExcel_CalcEngine_Logger
+ *
+ */
+ private $debugLog;
+
+ /**
+ * Flag to determine how formula errors should be handled
+ * If true, then a user error will be triggered
+ * If false, then an exception will be thrown
+ *
+ * @access public
+ * @var boolean
+ *
+ */
+ public $suppressFormulaErrors = FALSE;
+
+ /**
+ * Error message for any error that was raised/thrown by the calculation engine
+ *
+ * @access public
+ * @var string
+ *
+ */
+ public $formulaError = NULL;
+
+ /**
+ * An array of the nested cell references accessed by the calculation engine, used for the debug log
+ *
+ * @access private
+ * @var array of string
+ *
+ */
+ private $_cyclicReferenceStack;
+
+ /**
+ * Current iteration counter for cyclic formulae
+ * If the value is 0 (or less) then cyclic formulae will throw an exception,
+ * otherwise they will iterate to the limit defined here before returning a result
+ *
+ * @var integer
+ *
+ */
+ private $_cyclicFormulaCount = 0;
+
+ private $_cyclicFormulaCell = '';
+
+ /**
+ * Number of iterations for cyclic formulae
+ *
+ * @var integer
+ *
+ */
+ public $cyclicFormulaCount = 0;
+
+ /**
+ * Precision used for calculations
+ *
+ * @var integer
+ *
+ */
+ private $_savedPrecision = 14;
+
+
+ /**
+ * The current locale setting
+ *
+ * @var string
+ *
+ */
+ private static $_localeLanguage = 'en_us'; // US English (default locale)
+
+ /**
+ * List of available locale settings
+ * Note that this is read for the locale subdirectory only when requested
+ *
+ * @var string[]
+ *
+ */
+ private static $_validLocaleLanguages = array( 'en' // English (default language)
+ );
+ /**
+ * Locale-specific argument separator for function arguments
+ *
+ * @var string
+ *
+ */
+ private static $_localeArgumentSeparator = ',';
+ private static $_localeFunctions = array();
+
+ /**
+ * Locale-specific translations for Excel constants (True, False and Null)
+ *
+ * @var string[]
+ *
+ */
+ public static $_localeBoolean = array( 'TRUE' => 'TRUE',
+ 'FALSE' => 'FALSE',
+ 'NULL' => 'NULL'
+ );
+
+
+ /**
+ * Excel constant string translations to their PHP equivalents
+ * Constant conversion from text name/value to actual (datatyped) value
+ *
+ * @var string[]
+ *
+ */
+ private static $_ExcelConstants = array('TRUE' => TRUE,
+ 'FALSE' => FALSE,
+ 'NULL' => NULL
+ );
+
+ // PHPExcel functions
+ private static $_PHPExcelFunctions = array( // PHPExcel functions
+ 'ABS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'abs',
+ 'argumentCount' => '1'
+ ),
+ 'ACCRINT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::ACCRINT',
+ 'argumentCount' => '4-7'
+ ),
+ 'ACCRINTM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::ACCRINTM',
+ 'argumentCount' => '3-5'
+ ),
+ 'ACOS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'acos',
+ 'argumentCount' => '1'
+ ),
+ 'ACOSH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'acosh',
+ 'argumentCount' => '1'
+ ),
+ 'ADDRESS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::CELL_ADDRESS',
+ 'argumentCount' => '2-5'
+ ),
+ 'AMORDEGRC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::AMORDEGRC',
+ 'argumentCount' => '6,7'
+ ),
+ 'AMORLINC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::AMORLINC',
+ 'argumentCount' => '6,7'
+ ),
+ 'AND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::LOGICAL_AND',
+ 'argumentCount' => '1+'
+ ),
+ 'AREAS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'ASC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'ASIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'asin',
+ 'argumentCount' => '1'
+ ),
+ 'ASINH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'asinh',
+ 'argumentCount' => '1'
+ ),
+ 'ATAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'atan',
+ 'argumentCount' => '1'
+ ),
+ 'ATAN2' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::ATAN2',
+ 'argumentCount' => '2'
+ ),
+ 'ATANH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'atanh',
+ 'argumentCount' => '1'
+ ),
+ 'AVEDEV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::AVEDEV',
+ 'argumentCount' => '1+'
+ ),
+ 'AVERAGE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::AVERAGE',
+ 'argumentCount' => '1+'
+ ),
+ 'AVERAGEA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::AVERAGEA',
+ 'argumentCount' => '1+'
+ ),
+ 'AVERAGEIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::AVERAGEIF',
+ 'argumentCount' => '2,3'
+ ),
+ 'AVERAGEIFS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '3+'
+ ),
+ 'BAHTTEXT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'BESSELI' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BESSELI',
+ 'argumentCount' => '2'
+ ),
+ 'BESSELJ' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BESSELJ',
+ 'argumentCount' => '2'
+ ),
+ 'BESSELK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BESSELK',
+ 'argumentCount' => '2'
+ ),
+ 'BESSELY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BESSELY',
+ 'argumentCount' => '2'
+ ),
+ 'BETADIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::BETADIST',
+ 'argumentCount' => '3-5'
+ ),
+ 'BETAINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::BETAINV',
+ 'argumentCount' => '3-5'
+ ),
+ 'BIN2DEC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BINTODEC',
+ 'argumentCount' => '1'
+ ),
+ 'BIN2HEX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BINTOHEX',
+ 'argumentCount' => '1,2'
+ ),
+ 'BIN2OCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::BINTOOCT',
+ 'argumentCount' => '1,2'
+ ),
+ 'BINOMDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::BINOMDIST',
+ 'argumentCount' => '4'
+ ),
+ 'CEILING' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::CEILING',
+ 'argumentCount' => '2'
+ ),
+ 'CELL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1,2'
+ ),
+ 'CHAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::CHARACTER',
+ 'argumentCount' => '1'
+ ),
+ 'CHIDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CHIDIST',
+ 'argumentCount' => '2'
+ ),
+ 'CHIINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CHIINV',
+ 'argumentCount' => '2'
+ ),
+ 'CHITEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'CHOOSE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::CHOOSE',
+ 'argumentCount' => '2+'
+ ),
+ 'CLEAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::TRIMNONPRINTABLE',
+ 'argumentCount' => '1'
+ ),
+ 'CODE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::ASCIICODE',
+ 'argumentCount' => '1'
+ ),
+ 'COLUMN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::COLUMN',
+ 'argumentCount' => '-1',
+ 'passByReference' => array(TRUE)
+ ),
+ 'COLUMNS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::COLUMNS',
+ 'argumentCount' => '1'
+ ),
+ 'COMBIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::COMBIN',
+ 'argumentCount' => '2'
+ ),
+ 'COMPLEX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::COMPLEX',
+ 'argumentCount' => '2,3'
+ ),
+ 'CONCATENATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::CONCATENATE',
+ 'argumentCount' => '1+'
+ ),
+ 'CONFIDENCE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CONFIDENCE',
+ 'argumentCount' => '3'
+ ),
+ 'CONVERT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::CONVERTUOM',
+ 'argumentCount' => '3'
+ ),
+ 'CORREL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CORREL',
+ 'argumentCount' => '2'
+ ),
+ 'COS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'cos',
+ 'argumentCount' => '1'
+ ),
+ 'COSH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'cosh',
+ 'argumentCount' => '1'
+ ),
+ 'COUNT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::COUNT',
+ 'argumentCount' => '1+'
+ ),
+ 'COUNTA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::COUNTA',
+ 'argumentCount' => '1+'
+ ),
+ 'COUNTBLANK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::COUNTBLANK',
+ 'argumentCount' => '1'
+ ),
+ 'COUNTIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::COUNTIF',
+ 'argumentCount' => '2'
+ ),
+ 'COUNTIFS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'COUPDAYBS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPDAYBS',
+ 'argumentCount' => '3,4'
+ ),
+ 'COUPDAYS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPDAYS',
+ 'argumentCount' => '3,4'
+ ),
+ 'COUPDAYSNC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPDAYSNC',
+ 'argumentCount' => '3,4'
+ ),
+ 'COUPNCD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPNCD',
+ 'argumentCount' => '3,4'
+ ),
+ 'COUPNUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPNUM',
+ 'argumentCount' => '3,4'
+ ),
+ 'COUPPCD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::COUPPCD',
+ 'argumentCount' => '3,4'
+ ),
+ 'COVAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::COVAR',
+ 'argumentCount' => '2'
+ ),
+ 'CRITBINOM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CRITBINOM',
+ 'argumentCount' => '3'
+ ),
+ 'CUBEKPIMEMBER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBEMEMBER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBEMEMBERPROPERTY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBERANKEDMEMBER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBESET' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBESETCOUNT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUBEVALUE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_CUBE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'CUMIPMT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::CUMIPMT',
+ 'argumentCount' => '6'
+ ),
+ 'CUMPRINC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::CUMPRINC',
+ 'argumentCount' => '6'
+ ),
+ 'DATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DATE',
+ 'argumentCount' => '3'
+ ),
+ 'DATEDIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DATEDIF',
+ 'argumentCount' => '2,3'
+ ),
+ 'DATEVALUE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DATEVALUE',
+ 'argumentCount' => '1'
+ ),
+ 'DAVERAGE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DAVERAGE',
+ 'argumentCount' => '3'
+ ),
+ 'DAY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DAYOFMONTH',
+ 'argumentCount' => '1'
+ ),
+ 'DAYS360' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DAYS360',
+ 'argumentCount' => '2,3'
+ ),
+ 'DB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::DB',
+ 'argumentCount' => '4,5'
+ ),
+ 'DCOUNT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DCOUNT',
+ 'argumentCount' => '3'
+ ),
+ 'DCOUNTA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DCOUNTA',
+ 'argumentCount' => '3'
+ ),
+ 'DDB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::DDB',
+ 'argumentCount' => '4,5'
+ ),
+ 'DEC2BIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::DECTOBIN',
+ 'argumentCount' => '1,2'
+ ),
+ 'DEC2HEX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::DECTOHEX',
+ 'argumentCount' => '1,2'
+ ),
+ 'DEC2OCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::DECTOOCT',
+ 'argumentCount' => '1,2'
+ ),
+ 'DEGREES' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'rad2deg',
+ 'argumentCount' => '1'
+ ),
+ 'DELTA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::DELTA',
+ 'argumentCount' => '1,2'
+ ),
+ 'DEVSQ' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::DEVSQ',
+ 'argumentCount' => '1+'
+ ),
+ 'DGET' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DGET',
+ 'argumentCount' => '3'
+ ),
+ 'DISC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::DISC',
+ 'argumentCount' => '4,5'
+ ),
+ 'DMAX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DMAX',
+ 'argumentCount' => '3'
+ ),
+ 'DMIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DMIN',
+ 'argumentCount' => '3'
+ ),
+ 'DOLLAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::DOLLAR',
+ 'argumentCount' => '1,2'
+ ),
+ 'DOLLARDE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::DOLLARDE',
+ 'argumentCount' => '2'
+ ),
+ 'DOLLARFR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::DOLLARFR',
+ 'argumentCount' => '2'
+ ),
+ 'DPRODUCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DPRODUCT',
+ 'argumentCount' => '3'
+ ),
+ 'DSTDEV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DSTDEV',
+ 'argumentCount' => '3'
+ ),
+ 'DSTDEVP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DSTDEVP',
+ 'argumentCount' => '3'
+ ),
+ 'DSUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DSUM',
+ 'argumentCount' => '3'
+ ),
+ 'DURATION' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '5,6'
+ ),
+ 'DVAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DVAR',
+ 'argumentCount' => '3'
+ ),
+ 'DVARP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATABASE,
+ 'functionCall' => 'PHPExcel_Calculation_Database::DVARP',
+ 'argumentCount' => '3'
+ ),
+ 'EDATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::EDATE',
+ 'argumentCount' => '2'
+ ),
+ 'EFFECT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::EFFECT',
+ 'argumentCount' => '2'
+ ),
+ 'EOMONTH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::EOMONTH',
+ 'argumentCount' => '2'
+ ),
+ 'ERF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::ERF',
+ 'argumentCount' => '1,2'
+ ),
+ 'ERFC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::ERFC',
+ 'argumentCount' => '1'
+ ),
+ 'ERROR.TYPE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::ERROR_TYPE',
+ 'argumentCount' => '1'
+ ),
+ 'EVEN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::EVEN',
+ 'argumentCount' => '1'
+ ),
+ 'EXACT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'EXP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'exp',
+ 'argumentCount' => '1'
+ ),
+ 'EXPONDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::EXPONDIST',
+ 'argumentCount' => '3'
+ ),
+ 'FACT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::FACT',
+ 'argumentCount' => '1'
+ ),
+ 'FACTDOUBLE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::FACTDOUBLE',
+ 'argumentCount' => '1'
+ ),
+ 'FALSE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::FALSE',
+ 'argumentCount' => '0'
+ ),
+ 'FDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '3'
+ ),
+ 'FIND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::SEARCHSENSITIVE',
+ 'argumentCount' => '2,3'
+ ),
+ 'FINDB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::SEARCHSENSITIVE',
+ 'argumentCount' => '2,3'
+ ),
+ 'FINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '3'
+ ),
+ 'FISHER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::FISHER',
+ 'argumentCount' => '1'
+ ),
+ 'FISHERINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::FISHERINV',
+ 'argumentCount' => '1'
+ ),
+ 'FIXED' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::FIXEDFORMAT',
+ 'argumentCount' => '1-3'
+ ),
+ 'FLOOR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::FLOOR',
+ 'argumentCount' => '2'
+ ),
+ 'FORECAST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::FORECAST',
+ 'argumentCount' => '3'
+ ),
+ 'FREQUENCY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'FTEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'FV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::FV',
+ 'argumentCount' => '3-5'
+ ),
+ 'FVSCHEDULE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::FVSCHEDULE',
+ 'argumentCount' => '2'
+ ),
+ 'GAMMADIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::GAMMADIST',
+ 'argumentCount' => '4'
+ ),
+ 'GAMMAINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::GAMMAINV',
+ 'argumentCount' => '3'
+ ),
+ 'GAMMALN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::GAMMALN',
+ 'argumentCount' => '1'
+ ),
+ 'GCD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::GCD',
+ 'argumentCount' => '1+'
+ ),
+ 'GEOMEAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::GEOMEAN',
+ 'argumentCount' => '1+'
+ ),
+ 'GESTEP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::GESTEP',
+ 'argumentCount' => '1,2'
+ ),
+ 'GETPIVOTDATA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2+'
+ ),
+ 'GROWTH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::GROWTH',
+ 'argumentCount' => '1-4'
+ ),
+ 'HARMEAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::HARMEAN',
+ 'argumentCount' => '1+'
+ ),
+ 'HEX2BIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::HEXTOBIN',
+ 'argumentCount' => '1,2'
+ ),
+ 'HEX2DEC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::HEXTODEC',
+ 'argumentCount' => '1'
+ ),
+ 'HEX2OCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::HEXTOOCT',
+ 'argumentCount' => '1,2'
+ ),
+ 'HLOOKUP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '3,4'
+ ),
+ 'HOUR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::HOUROFDAY',
+ 'argumentCount' => '1'
+ ),
+ 'HYPERLINK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::HYPERLINK',
+ 'argumentCount' => '1,2',
+ 'passCellReference'=> TRUE
+ ),
+ 'HYPGEOMDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::HYPGEOMDIST',
+ 'argumentCount' => '4'
+ ),
+ 'IF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::STATEMENT_IF',
+ 'argumentCount' => '1-3'
+ ),
+ 'IFERROR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::IFERROR',
+ 'argumentCount' => '2'
+ ),
+ 'IMABS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMABS',
+ 'argumentCount' => '1'
+ ),
+ 'IMAGINARY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMAGINARY',
+ 'argumentCount' => '1'
+ ),
+ 'IMARGUMENT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMARGUMENT',
+ 'argumentCount' => '1'
+ ),
+ 'IMCONJUGATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMCONJUGATE',
+ 'argumentCount' => '1'
+ ),
+ 'IMCOS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMCOS',
+ 'argumentCount' => '1'
+ ),
+ 'IMDIV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMDIV',
+ 'argumentCount' => '2'
+ ),
+ 'IMEXP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMEXP',
+ 'argumentCount' => '1'
+ ),
+ 'IMLN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMLN',
+ 'argumentCount' => '1'
+ ),
+ 'IMLOG10' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMLOG10',
+ 'argumentCount' => '1'
+ ),
+ 'IMLOG2' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMLOG2',
+ 'argumentCount' => '1'
+ ),
+ 'IMPOWER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMPOWER',
+ 'argumentCount' => '2'
+ ),
+ 'IMPRODUCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMPRODUCT',
+ 'argumentCount' => '1+'
+ ),
+ 'IMREAL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMREAL',
+ 'argumentCount' => '1'
+ ),
+ 'IMSIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMSIN',
+ 'argumentCount' => '1'
+ ),
+ 'IMSQRT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMSQRT',
+ 'argumentCount' => '1'
+ ),
+ 'IMSUB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMSUB',
+ 'argumentCount' => '2'
+ ),
+ 'IMSUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::IMSUM',
+ 'argumentCount' => '1+'
+ ),
+ 'INDEX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::INDEX',
+ 'argumentCount' => '1-4'
+ ),
+ 'INDIRECT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::INDIRECT',
+ 'argumentCount' => '1,2',
+ 'passCellReference'=> TRUE
+ ),
+ 'INFO' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'INT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::INT',
+ 'argumentCount' => '1'
+ ),
+ 'INTERCEPT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::INTERCEPT',
+ 'argumentCount' => '2'
+ ),
+ 'INTRATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::INTRATE',
+ 'argumentCount' => '4,5'
+ ),
+ 'IPMT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::IPMT',
+ 'argumentCount' => '4-6'
+ ),
+ 'IRR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::IRR',
+ 'argumentCount' => '1,2'
+ ),
+ 'ISBLANK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_BLANK',
+ 'argumentCount' => '1'
+ ),
+ 'ISERR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_ERR',
+ 'argumentCount' => '1'
+ ),
+ 'ISERROR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_ERROR',
+ 'argumentCount' => '1'
+ ),
+ 'ISEVEN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_EVEN',
+ 'argumentCount' => '1'
+ ),
+ 'ISLOGICAL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_LOGICAL',
+ 'argumentCount' => '1'
+ ),
+ 'ISNA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_NA',
+ 'argumentCount' => '1'
+ ),
+ 'ISNONTEXT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_NONTEXT',
+ 'argumentCount' => '1'
+ ),
+ 'ISNUMBER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_NUMBER',
+ 'argumentCount' => '1'
+ ),
+ 'ISODD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_ODD',
+ 'argumentCount' => '1'
+ ),
+ 'ISPMT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::ISPMT',
+ 'argumentCount' => '4'
+ ),
+ 'ISREF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'ISTEXT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::IS_TEXT',
+ 'argumentCount' => '1'
+ ),
+ 'JIS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'KURT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::KURT',
+ 'argumentCount' => '1+'
+ ),
+ 'LARGE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::LARGE',
+ 'argumentCount' => '2'
+ ),
+ 'LCM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::LCM',
+ 'argumentCount' => '1+'
+ ),
+ 'LEFT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::LEFT',
+ 'argumentCount' => '1,2'
+ ),
+ 'LEFTB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::LEFT',
+ 'argumentCount' => '1,2'
+ ),
+ 'LEN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::STRINGLENGTH',
+ 'argumentCount' => '1'
+ ),
+ 'LENB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::STRINGLENGTH',
+ 'argumentCount' => '1'
+ ),
+ 'LINEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::LINEST',
+ 'argumentCount' => '1-4'
+ ),
+ 'LN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'log',
+ 'argumentCount' => '1'
+ ),
+ 'LOG' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::LOG_BASE',
+ 'argumentCount' => '1,2'
+ ),
+ 'LOG10' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'log10',
+ 'argumentCount' => '1'
+ ),
+ 'LOGEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::LOGEST',
+ 'argumentCount' => '1-4'
+ ),
+ 'LOGINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::LOGINV',
+ 'argumentCount' => '3'
+ ),
+ 'LOGNORMDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::LOGNORMDIST',
+ 'argumentCount' => '3'
+ ),
+ 'LOOKUP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::LOOKUP',
+ 'argumentCount' => '2,3'
+ ),
+ 'LOWER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::LOWERCASE',
+ 'argumentCount' => '1'
+ ),
+ 'MATCH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::MATCH',
+ 'argumentCount' => '2,3'
+ ),
+ 'MAX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MAX',
+ 'argumentCount' => '1+'
+ ),
+ 'MAXA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MAXA',
+ 'argumentCount' => '1+'
+ ),
+ 'MAXIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MAXIF',
+ 'argumentCount' => '2+'
+ ),
+ 'MDETERM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MDETERM',
+ 'argumentCount' => '1'
+ ),
+ 'MDURATION' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '5,6'
+ ),
+ 'MEDIAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MEDIAN',
+ 'argumentCount' => '1+'
+ ),
+ 'MEDIANIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2+'
+ ),
+ 'MID' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::MID',
+ 'argumentCount' => '3'
+ ),
+ 'MIDB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::MID',
+ 'argumentCount' => '3'
+ ),
+ 'MIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MIN',
+ 'argumentCount' => '1+'
+ ),
+ 'MINA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MINA',
+ 'argumentCount' => '1+'
+ ),
+ 'MINIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MINIF',
+ 'argumentCount' => '2+'
+ ),
+ 'MINUTE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::MINUTEOFHOUR',
+ 'argumentCount' => '1'
+ ),
+ 'MINVERSE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MINVERSE',
+ 'argumentCount' => '1'
+ ),
+ 'MIRR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::MIRR',
+ 'argumentCount' => '3'
+ ),
+ 'MMULT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MMULT',
+ 'argumentCount' => '2'
+ ),
+ 'MOD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MOD',
+ 'argumentCount' => '2'
+ ),
+ 'MODE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::MODE',
+ 'argumentCount' => '1+'
+ ),
+ 'MONTH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::MONTHOFYEAR',
+ 'argumentCount' => '1'
+ ),
+ 'MROUND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MROUND',
+ 'argumentCount' => '2'
+ ),
+ 'MULTINOMIAL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::MULTINOMIAL',
+ 'argumentCount' => '1+'
+ ),
+ 'N' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::N',
+ 'argumentCount' => '1'
+ ),
+ 'NA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::NA',
+ 'argumentCount' => '0'
+ ),
+ 'NEGBINOMDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::NEGBINOMDIST',
+ 'argumentCount' => '3'
+ ),
+ 'NETWORKDAYS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::NETWORKDAYS',
+ 'argumentCount' => '2+'
+ ),
+ 'NOMINAL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::NOMINAL',
+ 'argumentCount' => '2'
+ ),
+ 'NORMDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::NORMDIST',
+ 'argumentCount' => '4'
+ ),
+ 'NORMINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::NORMINV',
+ 'argumentCount' => '3'
+ ),
+ 'NORMSDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::NORMSDIST',
+ 'argumentCount' => '1'
+ ),
+ 'NORMSINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::NORMSINV',
+ 'argumentCount' => '1'
+ ),
+ 'NOT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::NOT',
+ 'argumentCount' => '1'
+ ),
+ 'NOW' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DATETIMENOW',
+ 'argumentCount' => '0'
+ ),
+ 'NPER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::NPER',
+ 'argumentCount' => '3-5'
+ ),
+ 'NPV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::NPV',
+ 'argumentCount' => '2+'
+ ),
+ 'OCT2BIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::OCTTOBIN',
+ 'argumentCount' => '1,2'
+ ),
+ 'OCT2DEC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::OCTTODEC',
+ 'argumentCount' => '1'
+ ),
+ 'OCT2HEX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_ENGINEERING,
+ 'functionCall' => 'PHPExcel_Calculation_Engineering::OCTTOHEX',
+ 'argumentCount' => '1,2'
+ ),
+ 'ODD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::ODD',
+ 'argumentCount' => '1'
+ ),
+ 'ODDFPRICE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '8,9'
+ ),
+ 'ODDFYIELD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '8,9'
+ ),
+ 'ODDLPRICE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '7,8'
+ ),
+ 'ODDLYIELD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '7,8'
+ ),
+ 'OFFSET' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::OFFSET',
+ 'argumentCount' => '3,5',
+ 'passCellReference'=> TRUE,
+ 'passByReference' => array(TRUE)
+ ),
+ 'OR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::LOGICAL_OR',
+ 'argumentCount' => '1+'
+ ),
+ 'PEARSON' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::CORREL',
+ 'argumentCount' => '2'
+ ),
+ 'PERCENTILE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::PERCENTILE',
+ 'argumentCount' => '2'
+ ),
+ 'PERCENTRANK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::PERCENTRANK',
+ 'argumentCount' => '2,3'
+ ),
+ 'PERMUT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::PERMUT',
+ 'argumentCount' => '2'
+ ),
+ 'PHONETIC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'PI' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'pi',
+ 'argumentCount' => '0'
+ ),
+ 'PMT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PMT',
+ 'argumentCount' => '3-5'
+ ),
+ 'POISSON' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::POISSON',
+ 'argumentCount' => '3'
+ ),
+ 'POWER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::POWER',
+ 'argumentCount' => '2'
+ ),
+ 'PPMT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PPMT',
+ 'argumentCount' => '4-6'
+ ),
+ 'PRICE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PRICE',
+ 'argumentCount' => '6,7'
+ ),
+ 'PRICEDISC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PRICEDISC',
+ 'argumentCount' => '4,5'
+ ),
+ 'PRICEMAT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PRICEMAT',
+ 'argumentCount' => '5,6'
+ ),
+ 'PROB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '3,4'
+ ),
+ 'PRODUCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::PRODUCT',
+ 'argumentCount' => '1+'
+ ),
+ 'PROPER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::PROPERCASE',
+ 'argumentCount' => '1'
+ ),
+ 'PV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::PV',
+ 'argumentCount' => '3-5'
+ ),
+ 'QUARTILE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::QUARTILE',
+ 'argumentCount' => '2'
+ ),
+ 'QUOTIENT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::QUOTIENT',
+ 'argumentCount' => '2'
+ ),
+ 'RADIANS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'deg2rad',
+ 'argumentCount' => '1'
+ ),
+ 'RAND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::RAND',
+ 'argumentCount' => '0'
+ ),
+ 'RANDBETWEEN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::RAND',
+ 'argumentCount' => '2'
+ ),
+ 'RANK' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::RANK',
+ 'argumentCount' => '2,3'
+ ),
+ 'RATE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::RATE',
+ 'argumentCount' => '3-6'
+ ),
+ 'RECEIVED' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::RECEIVED',
+ 'argumentCount' => '4-5'
+ ),
+ 'REPLACE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::REPLACE',
+ 'argumentCount' => '4'
+ ),
+ 'REPLACEB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::REPLACE',
+ 'argumentCount' => '4'
+ ),
+ 'REPT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'str_repeat',
+ 'argumentCount' => '2'
+ ),
+ 'RIGHT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::RIGHT',
+ 'argumentCount' => '1,2'
+ ),
+ 'RIGHTB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::RIGHT',
+ 'argumentCount' => '1,2'
+ ),
+ 'ROMAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::ROMAN',
+ 'argumentCount' => '1,2'
+ ),
+ 'ROUND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'round',
+ 'argumentCount' => '2'
+ ),
+ 'ROUNDDOWN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::ROUNDDOWN',
+ 'argumentCount' => '2'
+ ),
+ 'ROUNDUP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::ROUNDUP',
+ 'argumentCount' => '2'
+ ),
+ 'ROW' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::ROW',
+ 'argumentCount' => '-1',
+ 'passByReference' => array(TRUE)
+ ),
+ 'ROWS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::ROWS',
+ 'argumentCount' => '1'
+ ),
+ 'RSQ' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::RSQ',
+ 'argumentCount' => '2'
+ ),
+ 'RTD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1+'
+ ),
+ 'SEARCH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::SEARCHINSENSITIVE',
+ 'argumentCount' => '2,3'
+ ),
+ 'SEARCHB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::SEARCHINSENSITIVE',
+ 'argumentCount' => '2,3'
+ ),
+ 'SECOND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::SECONDOFMINUTE',
+ 'argumentCount' => '1'
+ ),
+ 'SERIESSUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SERIESSUM',
+ 'argumentCount' => '4'
+ ),
+ 'SIGN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SIGN',
+ 'argumentCount' => '1'
+ ),
+ 'SIN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'sin',
+ 'argumentCount' => '1'
+ ),
+ 'SINH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'sinh',
+ 'argumentCount' => '1'
+ ),
+ 'SKEW' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::SKEW',
+ 'argumentCount' => '1+'
+ ),
+ 'SLN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::SLN',
+ 'argumentCount' => '3'
+ ),
+ 'SLOPE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::SLOPE',
+ 'argumentCount' => '2'
+ ),
+ 'SMALL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::SMALL',
+ 'argumentCount' => '2'
+ ),
+ 'SQRT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'sqrt',
+ 'argumentCount' => '1'
+ ),
+ 'SQRTPI' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SQRTPI',
+ 'argumentCount' => '1'
+ ),
+ 'STANDARDIZE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STANDARDIZE',
+ 'argumentCount' => '3'
+ ),
+ 'STDEV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STDEV',
+ 'argumentCount' => '1+'
+ ),
+ 'STDEVA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STDEVA',
+ 'argumentCount' => '1+'
+ ),
+ 'STDEVP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STDEVP',
+ 'argumentCount' => '1+'
+ ),
+ 'STDEVPA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STDEVPA',
+ 'argumentCount' => '1+'
+ ),
+ 'STEYX' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::STEYX',
+ 'argumentCount' => '2'
+ ),
+ 'SUBSTITUTE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::SUBSTITUTE',
+ 'argumentCount' => '3,4'
+ ),
+ 'SUBTOTAL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUBTOTAL',
+ 'argumentCount' => '2+'
+ ),
+ 'SUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUM',
+ 'argumentCount' => '1+'
+ ),
+ 'SUMIF' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMIF',
+ 'argumentCount' => '2,3'
+ ),
+ 'SUMIFS' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '?'
+ ),
+ 'SUMPRODUCT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMPRODUCT',
+ 'argumentCount' => '1+'
+ ),
+ 'SUMSQ' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMSQ',
+ 'argumentCount' => '1+'
+ ),
+ 'SUMX2MY2' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMX2MY2',
+ 'argumentCount' => '2'
+ ),
+ 'SUMX2PY2' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMX2PY2',
+ 'argumentCount' => '2'
+ ),
+ 'SUMXMY2' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::SUMXMY2',
+ 'argumentCount' => '2'
+ ),
+ 'SYD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::SYD',
+ 'argumentCount' => '4'
+ ),
+ 'T' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::RETURNSTRING',
+ 'argumentCount' => '1'
+ ),
+ 'TAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'tan',
+ 'argumentCount' => '1'
+ ),
+ 'TANH' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'tanh',
+ 'argumentCount' => '1'
+ ),
+ 'TBILLEQ' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::TBILLEQ',
+ 'argumentCount' => '3'
+ ),
+ 'TBILLPRICE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::TBILLPRICE',
+ 'argumentCount' => '3'
+ ),
+ 'TBILLYIELD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::TBILLYIELD',
+ 'argumentCount' => '3'
+ ),
+ 'TDIST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::TDIST',
+ 'argumentCount' => '3'
+ ),
+ 'TEXT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::TEXTFORMAT',
+ 'argumentCount' => '2'
+ ),
+ 'TIME' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::TIME',
+ 'argumentCount' => '3'
+ ),
+ 'TIMEVALUE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::TIMEVALUE',
+ 'argumentCount' => '1'
+ ),
+ 'TINV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::TINV',
+ 'argumentCount' => '2'
+ ),
+ 'TODAY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DATENOW',
+ 'argumentCount' => '0'
+ ),
+ 'TRANSPOSE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::TRANSPOSE',
+ 'argumentCount' => '1'
+ ),
+ 'TREND' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::TREND',
+ 'argumentCount' => '1-4'
+ ),
+ 'TRIM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::TRIMSPACES',
+ 'argumentCount' => '1'
+ ),
+ 'TRIMMEAN' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::TRIMMEAN',
+ 'argumentCount' => '2'
+ ),
+ 'TRUE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOGICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Logical::TRUE',
+ 'argumentCount' => '0'
+ ),
+ 'TRUNC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_MATH_AND_TRIG,
+ 'functionCall' => 'PHPExcel_Calculation_MathTrig::TRUNC',
+ 'argumentCount' => '1,2'
+ ),
+ 'TTEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '4'
+ ),
+ 'TYPE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::TYPE',
+ 'argumentCount' => '1'
+ ),
+ 'UPPER' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_TextData::UPPERCASE',
+ 'argumentCount' => '1'
+ ),
+ 'USDOLLAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '2'
+ ),
+ 'VALUE' => array('category' => PHPExcel_Calculation_Function::CATEGORY_TEXT_AND_DATA,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '1'
+ ),
+ 'VAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::VARFunc',
+ 'argumentCount' => '1+'
+ ),
+ 'VARA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::VARA',
+ 'argumentCount' => '1+'
+ ),
+ 'VARP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::VARP',
+ 'argumentCount' => '1+'
+ ),
+ 'VARPA' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::VARPA',
+ 'argumentCount' => '1+'
+ ),
+ 'VDB' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '5-7'
+ ),
+ 'VERSION' => array('category' => PHPExcel_Calculation_Function::CATEGORY_INFORMATION,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::VERSION',
+ 'argumentCount' => '0'
+ ),
+ 'VLOOKUP' => array('category' => PHPExcel_Calculation_Function::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => 'PHPExcel_Calculation_LookupRef::VLOOKUP',
+ 'argumentCount' => '3,4'
+ ),
+ 'WEEKDAY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::DAYOFWEEK',
+ 'argumentCount' => '1,2'
+ ),
+ 'WEEKNUM' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::WEEKOFYEAR',
+ 'argumentCount' => '1,2'
+ ),
+ 'WEIBULL' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::WEIBULL',
+ 'argumentCount' => '4'
+ ),
+ 'WORKDAY' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::WORKDAY',
+ 'argumentCount' => '2+'
+ ),
+ 'XIRR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::XIRR',
+ 'argumentCount' => '2,3'
+ ),
+ 'XNPV' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::XNPV',
+ 'argumentCount' => '3'
+ ),
+ 'YEAR' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::YEAR',
+ 'argumentCount' => '1'
+ ),
+ 'YEARFRAC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_DATE_AND_TIME,
+ 'functionCall' => 'PHPExcel_Calculation_DateTime::YEARFRAC',
+ 'argumentCount' => '2,3'
+ ),
+ 'YIELD' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Functions::DUMMY',
+ 'argumentCount' => '6,7'
+ ),
+ 'YIELDDISC' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::YIELDDISC',
+ 'argumentCount' => '4,5'
+ ),
+ 'YIELDMAT' => array('category' => PHPExcel_Calculation_Function::CATEGORY_FINANCIAL,
+ 'functionCall' => 'PHPExcel_Calculation_Financial::YIELDMAT',
+ 'argumentCount' => '5,6'
+ ),
+ 'ZTEST' => array('category' => PHPExcel_Calculation_Function::CATEGORY_STATISTICAL,
+ 'functionCall' => 'PHPExcel_Calculation_Statistical::ZTEST',
+ 'argumentCount' => '2-3'
+ )
+ );
+
+
+ // Internal functions used for special control purposes
+ private static $_controlFunctions = array(
+ 'MKMATRIX' => array('argumentCount' => '*',
+ 'functionCall' => 'self::_mkMatrix'
+ )
+ );
+
+
+
+
+ private function __construct(PHPExcel $workbook = NULL) {
+ $setPrecision = (PHP_INT_SIZE == 4) ? 14 : 16;
+ $this->_savedPrecision = ini_get('precision');
+ if ($this->_savedPrecision < $setPrecision) {
+ ini_set('precision',$setPrecision);
+ }
+
+ if ($workbook !== NULL) {
+ self::$_workbookSets[$workbook->getID()] = $this;
+ }
+
+ $this->_workbook = $workbook;
+ $this->_cyclicReferenceStack = new PHPExcel_CalcEngine_CyclicReferenceStack();
+ $this->_debugLog = new PHPExcel_CalcEngine_Logger($this->_cyclicReferenceStack);
+ } // function __construct()
+
+
+ public function __destruct() {
+ if ($this->_savedPrecision != ini_get('precision')) {
+ ini_set('precision',$this->_savedPrecision);
+ }
+ }
+
+ private static function _loadLocales() {
+ $localeFileDirectory = PHPEXCEL_ROOT.'PHPExcel/locale/';
+ foreach (glob($localeFileDirectory.'/*',GLOB_ONLYDIR) as $filename) {
+ $filename = substr($filename,strlen($localeFileDirectory)+1);
+ if ($filename != 'en') {
+ self::$_validLocaleLanguages[] = $filename;
+ }
+ }
+ }
+
+ /**
+ * Get an instance of this class
+ *
+ * @access public
+ * @param PHPExcel $workbook Injected workbook for working with a PHPExcel object,
+ * or NULL to create a standalone claculation engine
+ * @return PHPExcel_Calculation
+ */
+ public static function getInstance(PHPExcel $workbook = NULL) {
+ if ($workbook !== NULL) {
+ if (isset(self::$_workbookSets[$workbook->getID()])) {
+ return self::$_workbookSets[$workbook->getID()];
+ }
+ return new PHPExcel_Calculation($workbook);
+ }
+
+ if (!isset(self::$_instance) || (self::$_instance === NULL)) {
+ self::$_instance = new PHPExcel_Calculation();
+ }
+
+ return self::$_instance;
+ } // function getInstance()
+
+ /**
+ * Unset an instance of this class
+ *
+ * @access public
+ * @param PHPExcel $workbook Injected workbook identifying the instance to unset
+ */
+ public static function unsetInstance(PHPExcel $workbook = NULL) {
+ if ($workbook !== NULL) {
+ if (isset(self::$_workbookSets[$workbook->getID()])) {
+ unset(self::$_workbookSets[$workbook->getID()]);
+ }
+ }
+ }
+
+ /**
+ * Flush the calculation cache for any existing instance of this class
+ * but only if a PHPExcel_Calculation instance exists
+ *
+ * @access public
+ * @return null
+ */
+ public function flushInstance() {
+ $this->clearCalculationCache();
+ } // function flushInstance()
+
+
+ /**
+ * Get the debuglog for this claculation engine instance
+ *
+ * @access public
+ * @return PHPExcel_CalcEngine_Logger
+ */
+ public function getDebugLog() {
+ return $this->_debugLog;
+ }
+
+ /**
+ * __clone implementation. Cloning should not be allowed in a Singleton!
+ *
+ * @access public
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public final function __clone() {
+ throw new PHPExcel_Calculation_Exception ('Cloning the calculation engine is not allowed!');
+ } // function __clone()
+
+
+ /**
+ * Return the locale-specific translation of TRUE
+ *
+ * @access public
+ * @return string locale-specific translation of TRUE
+ */
+ public static function getTRUE() {
+ return self::$_localeBoolean['TRUE'];
+ }
+
+ /**
+ * Return the locale-specific translation of FALSE
+ *
+ * @access public
+ * @return string locale-specific translation of FALSE
+ */
+ public static function getFALSE() {
+ return self::$_localeBoolean['FALSE'];
+ }
+
+ /**
+ * Set the Array Return Type (Array or Value of first element in the array)
+ *
+ * @access public
+ * @param string $returnType Array return type
+ * @return boolean Success or failure
+ */
+ public static function setArrayReturnType($returnType) {
+ if (($returnType == self::RETURN_ARRAY_AS_VALUE) ||
+ ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
+ ($returnType == self::RETURN_ARRAY_AS_ARRAY)) {
+ self::$returnArrayAsType = $returnType;
+ return TRUE;
+ }
+ return FALSE;
+ } // function setArrayReturnType()
+
+
+ /**
+ * Return the Array Return Type (Array or Value of first element in the array)
+ *
+ * @access public
+ * @return string $returnType Array return type
+ */
+ public static function getArrayReturnType() {
+ return self::$returnArrayAsType;
+ } // function getArrayReturnType()
+
+
+ /**
+ * Is calculation caching enabled?
+ *
+ * @access public
+ * @return boolean
+ */
+ public function getCalculationCacheEnabled() {
+ return $this->_calculationCacheEnabled;
+ } // function getCalculationCacheEnabled()
+
+ /**
+ * Enable/disable calculation cache
+ *
+ * @access public
+ * @param boolean $pValue
+ */
+ public function setCalculationCacheEnabled($pValue = TRUE) {
+ $this->_calculationCacheEnabled = $pValue;
+ $this->clearCalculationCache();
+ } // function setCalculationCacheEnabled()
+
+
+ /**
+ * Enable calculation cache
+ */
+ public function enableCalculationCache() {
+ $this->setCalculationCacheEnabled(TRUE);
+ } // function enableCalculationCache()
+
+
+ /**
+ * Disable calculation cache
+ */
+ public function disableCalculationCache() {
+ $this->setCalculationCacheEnabled(FALSE);
+ } // function disableCalculationCache()
+
+
+ /**
+ * Clear calculation cache
+ */
+ public function clearCalculationCache() {
+ $this->_calculationCache = array();
+ } // function clearCalculationCache()
+
+ /**
+ * Clear calculation cache for a specified worksheet
+ *
+ * @param string $worksheetName
+ */
+ public function clearCalculationCacheForWorksheet($worksheetName) {
+ if (isset($this->_calculationCache[$worksheetName])) {
+ unset($this->_calculationCache[$worksheetName]);
+ }
+ } // function clearCalculationCacheForWorksheet()
+
+ /**
+ * Rename calculation cache for a specified worksheet
+ *
+ * @param string $fromWorksheetName
+ * @param string $toWorksheetName
+ */
+ public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName) {
+ if (isset($this->_calculationCache[$fromWorksheetName])) {
+ $this->_calculationCache[$toWorksheetName] = &$this->_calculationCache[$fromWorksheetName];
+ unset($this->_calculationCache[$fromWorksheetName]);
+ }
+ } // function renameCalculationCacheForWorksheet()
+
+
+ /**
+ * Get the currently defined locale code
+ *
+ * @return string
+ */
+ public function getLocale() {
+ return self::$_localeLanguage;
+ } // function getLocale()
+
+
+ /**
+ * Set the locale code
+ *
+ * @param string $locale The locale to use for formula translation
+ * @return boolean
+ */
+ public function setLocale($locale = 'en_us') {
+ // Identify our locale and language
+ $language = $locale = strtolower($locale);
+ if (strpos($locale,'_') !== FALSE) {
+ list($language) = explode('_',$locale);
+ }
+
+ if (count(self::$_validLocaleLanguages) == 1)
+ self::_loadLocales();
+
+ // Test whether we have any language data for this language (any locale)
+ if (in_array($language,self::$_validLocaleLanguages)) {
+ // initialise language/locale settings
+ self::$_localeFunctions = array();
+ self::$_localeArgumentSeparator = ',';
+ self::$_localeBoolean = array('TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL');
+ // Default is English, if user isn't requesting english, then read the necessary data from the locale files
+ if ($locale != 'en_us') {
+ // Search for a file with a list of function names for locale
+ $functionNamesFile = PHPEXCEL_ROOT . 'PHPExcel'.DIRECTORY_SEPARATOR.'locale'.DIRECTORY_SEPARATOR.str_replace('_',DIRECTORY_SEPARATOR,$locale).DIRECTORY_SEPARATOR.'functions';
+ if (!file_exists($functionNamesFile)) {
+ // If there isn't a locale specific function file, look for a language specific function file
+ $functionNamesFile = PHPEXCEL_ROOT . 'PHPExcel'.DIRECTORY_SEPARATOR.'locale'.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'functions';
+ if (!file_exists($functionNamesFile)) {
+ return FALSE;
+ }
+ }
+ // Retrieve the list of locale or language specific function names
+ $localeFunctions = file($functionNamesFile,FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ foreach ($localeFunctions as $localeFunction) {
+ list($localeFunction) = explode('##',$localeFunction); // Strip out comments
+ if (strpos($localeFunction,'=') !== FALSE) {
+ list($fName,$lfName) = explode('=',$localeFunction);
+ $fName = trim($fName);
+ $lfName = trim($lfName);
+ if ((isset(self::$_PHPExcelFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
+ self::$_localeFunctions[$fName] = $lfName;
+ }
+ }
+ }
+ // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
+ if (isset(self::$_localeFunctions['TRUE'])) { self::$_localeBoolean['TRUE'] = self::$_localeFunctions['TRUE']; }
+ if (isset(self::$_localeFunctions['FALSE'])) { self::$_localeBoolean['FALSE'] = self::$_localeFunctions['FALSE']; }
+
+ $configFile = PHPEXCEL_ROOT . 'PHPExcel'.DIRECTORY_SEPARATOR.'locale'.DIRECTORY_SEPARATOR.str_replace('_',DIRECTORY_SEPARATOR,$locale).DIRECTORY_SEPARATOR.'config';
+ if (!file_exists($configFile)) {
+ $configFile = PHPEXCEL_ROOT . 'PHPExcel'.DIRECTORY_SEPARATOR.'locale'.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'config';
+ }
+ if (file_exists($configFile)) {
+ $localeSettings = file($configFile,FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ foreach ($localeSettings as $localeSetting) {
+ list($localeSetting) = explode('##',$localeSetting); // Strip out comments
+ if (strpos($localeSetting,'=') !== FALSE) {
+ list($settingName,$settingValue) = explode('=',$localeSetting);
+ $settingName = strtoupper(trim($settingName));
+ switch ($settingName) {
+ case 'ARGUMENTSEPARATOR' :
+ self::$_localeArgumentSeparator = trim($settingValue);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
+ self::$functionReplaceFromLocale = self::$functionReplaceToLocale = NULL;
+ self::$_localeLanguage = $locale;
+ return TRUE;
+ }
+ return FALSE;
+ } // function setLocale()
+
+
+
+ public static function _translateSeparator($fromSeparator,$toSeparator,$formula,&$inBraces) {
+ $strlen = mb_strlen($formula);
+ for ($i = 0; $i < $strlen; ++$i) {
+ $chr = mb_substr($formula,$i,1);
+ switch ($chr) {
+ case '{' : $inBraces = TRUE;
+ break;
+ case '}' : $inBraces = FALSE;
+ break;
+ case $fromSeparator :
+ if (!$inBraces) {
+ $formula = mb_substr($formula,0,$i).$toSeparator.mb_substr($formula,$i+1);
+ }
+ }
+ }
+ return $formula;
+ }
+
+ private static function _translateFormula($from,$to,$formula,$fromSeparator,$toSeparator) {
+ // Convert any Excel function names to the required language
+ if (self::$_localeLanguage !== 'en_us') {
+ $inBraces = FALSE;
+ // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
+ if (strpos($formula,'"') !== FALSE) {
+ // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
+ // the formula
+ $temp = explode('"',$formula);
+ $i = FALSE;
+ foreach($temp as &$value) {
+ // Only count/replace in alternating array entries
+ if ($i = !$i) {
+ $value = preg_replace($from,$to,$value);
+ $value = self::_translateSeparator($fromSeparator,$toSeparator,$value,$inBraces);
+ }
+ }
+ unset($value);
+ // Then rebuild the formula string
+ $formula = implode('"',$temp);
+ } else {
+ // If there's no quoted strings, then we do a simple count/replace
+ $formula = preg_replace($from,$to,$formula);
+ $formula = self::_translateSeparator($fromSeparator,$toSeparator,$formula,$inBraces);
+ }
+ }
+
+ return $formula;
+ }
+
+ private static $functionReplaceFromExcel = NULL;
+ private static $functionReplaceToLocale = NULL;
+
+ public function _translateFormulaToLocale($formula) {
+ if (self::$functionReplaceFromExcel === NULL) {
+ self::$functionReplaceFromExcel = array();
+ foreach(array_keys(self::$_localeFunctions) as $excelFunctionName) {
+ self::$functionReplaceFromExcel[] = '/(@?[^\w\.])'.preg_quote($excelFunctionName).'([\s]*\()/Ui';
+ }
+ foreach(array_keys(self::$_localeBoolean) as $excelBoolean) {
+ self::$functionReplaceFromExcel[] = '/(@?[^\w\.])'.preg_quote($excelBoolean).'([^\w\.])/Ui';
+ }
+
+ }
+
+ if (self::$functionReplaceToLocale === NULL) {
+ self::$functionReplaceToLocale = array();
+ foreach(array_values(self::$_localeFunctions) as $localeFunctionName) {
+ self::$functionReplaceToLocale[] = '$1'.trim($localeFunctionName).'$2';
+ }
+ foreach(array_values(self::$_localeBoolean) as $localeBoolean) {
+ self::$functionReplaceToLocale[] = '$1'.trim($localeBoolean).'$2';
+ }
+ }
+
+ return self::_translateFormula(self::$functionReplaceFromExcel,self::$functionReplaceToLocale,$formula,',',self::$_localeArgumentSeparator);
+ } // function _translateFormulaToLocale()
+
+
+ private static $functionReplaceFromLocale = NULL;
+ private static $functionReplaceToExcel = NULL;
+
+ public function _translateFormulaToEnglish($formula) {
+ if (self::$functionReplaceFromLocale === NULL) {
+ self::$functionReplaceFromLocale = array();
+ foreach(array_values(self::$_localeFunctions) as $localeFunctionName) {
+ self::$functionReplaceFromLocale[] = '/(@?[^\w\.])'.preg_quote($localeFunctionName).'([\s]*\()/Ui';
+ }
+ foreach(array_values(self::$_localeBoolean) as $excelBoolean) {
+ self::$functionReplaceFromLocale[] = '/(@?[^\w\.])'.preg_quote($excelBoolean).'([^\w\.])/Ui';
+ }
+ }
+
+ if (self::$functionReplaceToExcel === NULL) {
+ self::$functionReplaceToExcel = array();
+ foreach(array_keys(self::$_localeFunctions) as $excelFunctionName) {
+ self::$functionReplaceToExcel[] = '$1'.trim($excelFunctionName).'$2';
+ }
+ foreach(array_keys(self::$_localeBoolean) as $excelBoolean) {
+ self::$functionReplaceToExcel[] = '$1'.trim($excelBoolean).'$2';
+ }
+ }
+
+ return self::_translateFormula(self::$functionReplaceFromLocale,self::$functionReplaceToExcel,$formula,self::$_localeArgumentSeparator,',');
+ } // function _translateFormulaToEnglish()
+
+
+ public static function _localeFunc($function) {
+ if (self::$_localeLanguage !== 'en_us') {
+ $functionName = trim($function,'(');
+ if (isset(self::$_localeFunctions[$functionName])) {
+ $brace = ($functionName != $function);
+ $function = self::$_localeFunctions[$functionName];
+ if ($brace) { $function .= '('; }
+ }
+ }
+ return $function;
+ }
+
+
+
+
+ /**
+ * Wrap string values in quotes
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public static function _wrapResult($value) {
+ if (is_string($value)) {
+ // Error values cannot be "wrapped"
+ if (preg_match('/^'.self::CALCULATION_REGEXP_ERROR.'$/i', $value, $match)) {
+ // Return Excel errors "as is"
+ return $value;
+ }
+ // Return strings wrapped in quotes
+ return '"'.$value.'"';
+ // Convert numeric errors to NaN error
+ } else if((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
+ return PHPExcel_Calculation_Functions::NaN();
+ }
+
+ return $value;
+ } // function _wrapResult()
+
+
+ /**
+ * Remove quotes used as a wrapper to identify string values
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public static function _unwrapResult($value) {
+ if (is_string($value)) {
+ if ((isset($value{0})) && ($value{0} == '"') && (substr($value,-1) == '"')) {
+ return substr($value,1,-1);
+ }
+ // Convert numeric errors to NaN error
+ } else if((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
+ return PHPExcel_Calculation_Functions::NaN();
+ }
+ return $value;
+ } // function _unwrapResult()
+
+
+
+
+ /**
+ * Calculate cell value (using formula from a cell ID)
+ * Retained for backward compatibility
+ *
+ * @access public
+ * @param PHPExcel_Cell $pCell Cell to calculate
+ * @return mixed
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function calculate(PHPExcel_Cell $pCell = NULL) {
+ try {
+ return $this->calculateCellValue($pCell);
+ } catch (PHPExcel_Exception $e) {
+ throw new PHPExcel_Calculation_Exception($e->getMessage());
+ }
+ } // function calculate()
+
+
+ /**
+ * Calculate the value of a cell formula
+ *
+ * @access public
+ * @param PHPExcel_Cell $pCell Cell to calculate
+ * @param Boolean $resetLog Flag indicating whether the debug log should be reset or not
+ * @return mixed
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function calculateCellValue(PHPExcel_Cell $pCell = NULL, $resetLog = TRUE) {
+ if ($pCell === NULL) {
+ return NULL;
+ }
+
+ $returnArrayAsType = self::$returnArrayAsType;
+ if ($resetLog) {
+ // Initialise the logging settings if requested
+ $this->formulaError = null;
+ $this->_debugLog->clearLog();
+ $this->_cyclicReferenceStack->clear();
+ $this->_cyclicFormulaCount = 1;
+
+ self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
+ }
+
+ // Execute the calculation for the cell formula
+ try {
+ $result = self::_unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
+ } catch (PHPExcel_Exception $e) {
+ throw new PHPExcel_Calculation_Exception($e->getMessage());
+ }
+
+ if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
+ self::$returnArrayAsType = $returnArrayAsType;
+ $testResult = PHPExcel_Calculation_Functions::flattenArray($result);
+ if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
+ return PHPExcel_Calculation_Functions::VALUE();
+ }
+ // If there's only a single cell in the array, then we allow it
+ if (count($testResult) != 1) {
+ // If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
+ $r = array_keys($result);
+ $r = array_shift($r);
+ if (!is_numeric($r)) { return PHPExcel_Calculation_Functions::VALUE(); }
+ if (is_array($result[$r])) {
+ $c = array_keys($result[$r]);
+ $c = array_shift($c);
+ if (!is_numeric($c)) {
+ return PHPExcel_Calculation_Functions::VALUE();
+ }
+ }
+ }
+ $result = array_shift($testResult);
+ }
+ self::$returnArrayAsType = $returnArrayAsType;
+
+
+ if ($result === NULL) {
+ return 0;
+ } elseif((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
+ return PHPExcel_Calculation_Functions::NaN();
+ }
+ return $result;
+ } // function calculateCellValue(
+
+
+ /**
+ * Validate and parse a formula string
+ *
+ * @param string $formula Formula to parse
+ * @return array
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function parseFormula($formula) {
+ // Basic validation that this is indeed a formula
+ // We return an empty array if not
+ $formula = trim($formula);
+ if ((!isset($formula{0})) || ($formula{0} != '=')) return array();
+ $formula = ltrim(substr($formula,1));
+ if (!isset($formula{0})) return array();
+
+ // Parse the formula and return the token stack
+ return $this->_parseFormula($formula);
+ } // function parseFormula()
+
+
+ /**
+ * Calculate the value of a formula
+ *
+ * @param string $formula Formula to parse
+ * @param string $cellID Address of the cell to calculate
+ * @param PHPExcel_Cell $pCell Cell to calculate
+ * @return mixed
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function calculateFormula($formula, $cellID=NULL, PHPExcel_Cell $pCell = NULL) {
+ // Initialise the logging settings
+ $this->formulaError = null;
+ $this->_debugLog->clearLog();
+ $this->_cyclicReferenceStack->clear();
+
+ // Disable calculation cacheing because it only applies to cell calculations, not straight formulae
+ // But don't actually flush any cache
+ $resetCache = $this->getCalculationCacheEnabled();
+ $this->_calculationCacheEnabled = FALSE;
+ // Execute the calculation
+ try {
+ $result = self::_unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
+ } catch (PHPExcel_Exception $e) {
+ throw new PHPExcel_Calculation_Exception($e->getMessage());
+ }
+
+ // Reset calculation cacheing to its previous state
+ $this->_calculationCacheEnabled = $resetCache;
+
+ return $result;
+ } // function calculateFormula()
+
+
+ public function getValueFromCache($worksheetName, $cellID, &$cellValue) {
+ // Is calculation cacheing enabled?
+ // Is the value present in calculation cache?
+//echo 'Test cache for ',$worksheetName,'!',$cellID,PHP_EOL;
+ $this->_debugLog->writeDebugLog('Testing cache value for cell ', $worksheetName, '!', $cellID);
+ if (($this->_calculationCacheEnabled) && (isset($this->_calculationCache[$worksheetName][$cellID]))) {
+//echo 'Retrieve from cache',PHP_EOL;
+ $this->_debugLog->writeDebugLog('Retrieving value for cell ', $worksheetName, '!', $cellID, ' from cache');
+ // Return the cached result
+ $cellValue = $this->_calculationCache[$worksheetName][$cellID];
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ public function saveValueToCache($worksheetName, $cellID, $cellValue) {
+ if ($this->_calculationCacheEnabled) {
+ $this->_calculationCache[$worksheetName][$cellID] = $cellValue;
+ }
+ }
+
+ /**
+ * Parse a cell formula and calculate its value
+ *
+ * @param string $formula The formula to parse and calculate
+ * @param string $cellID The ID (e.g. A3) of the cell that we are calculating
+ * @param PHPExcel_Cell $pCell Cell to calculate
+ * @return mixed
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function _calculateFormulaValue($formula, $cellID=null, PHPExcel_Cell $pCell = null) {
+ $cellValue = '';
+
+ // Basic validation that this is indeed a formula
+ // We simply return the cell value if not
+ $formula = trim($formula);
+ if ($formula{0} != '=') return self::_wrapResult($formula);
+ $formula = ltrim(substr($formula,1));
+ if (!isset($formula{0})) return self::_wrapResult($formula);
+
+ $pCellParent = ($pCell !== NULL) ? $pCell->getWorksheet() : NULL;
+ $wsTitle = ($pCellParent !== NULL) ? $pCellParent->getTitle() : "\x00Wrk";
+
+ if (($cellID !== NULL) && ($this->getValueFromCache($wsTitle, $cellID, $cellValue))) {
+ return $cellValue;
+ }
+
+ if (($wsTitle{0} !== "\x00") && ($this->_cyclicReferenceStack->onStack($wsTitle.'!'.$cellID))) {
+ if ($this->cyclicFormulaCount <= 0) {
+ return $this->_raiseFormulaError('Cyclic Reference in Formula');
+ } elseif (($this->_cyclicFormulaCount >= $this->cyclicFormulaCount) &&
+ ($this->_cyclicFormulaCell == $wsTitle.'!'.$cellID)) {
+ return $cellValue;
+ } elseif ($this->_cyclicFormulaCell == $wsTitle.'!'.$cellID) {
+ ++$this->_cyclicFormulaCount;
+ if ($this->_cyclicFormulaCount >= $this->cyclicFormulaCount) {
+ return $cellValue;
+ }
+ } elseif ($this->_cyclicFormulaCell == '') {
+ $this->_cyclicFormulaCell = $wsTitle.'!'.$cellID;
+ if ($this->_cyclicFormulaCount >= $this->cyclicFormulaCount) {
+ return $cellValue;
+ }
+ }
+ }
+
+ // Parse the formula onto the token stack and calculate the value
+ $this->_cyclicReferenceStack->push($wsTitle.'!'.$cellID);
+ $cellValue = $this->_processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell);
+ $this->_cyclicReferenceStack->pop();
+
+ // Save to calculation cache
+ if ($cellID !== NULL) {
+ $this->saveValueToCache($wsTitle, $cellID, $cellValue);
+ }
+
+ // Return the calculated value
+ return $cellValue;
+ } // function _calculateFormulaValue()
+
+
+ /**
+ * Ensure that paired matrix operands are both matrices and of the same size
+ *
+ * @param mixed &$operand1 First matrix operand
+ * @param mixed &$operand2 Second matrix operand
+ * @param integer $resize Flag indicating whether the matrices should be resized to match
+ * and (if so), whether the smaller dimension should grow or the
+ * larger should shrink.
+ * 0 = no resize
+ * 1 = shrink to fit
+ * 2 = extend to fit
+ */
+ private static function _checkMatrixOperands(&$operand1,&$operand2,$resize = 1) {
+ // Examine each of the two operands, and turn them into an array if they aren't one already
+ // Note that this function should only be called if one or both of the operand is already an array
+ if (!is_array($operand1)) {
+ list($matrixRows,$matrixColumns) = self::_getMatrixDimensions($operand2);
+ $operand1 = array_fill(0,$matrixRows,array_fill(0,$matrixColumns,$operand1));
+ $resize = 0;
+ } elseif (!is_array($operand2)) {
+ list($matrixRows,$matrixColumns) = self::_getMatrixDimensions($operand1);
+ $operand2 = array_fill(0,$matrixRows,array_fill(0,$matrixColumns,$operand2));
+ $resize = 0;
+ }
+
+ list($matrix1Rows,$matrix1Columns) = self::_getMatrixDimensions($operand1);
+ list($matrix2Rows,$matrix2Columns) = self::_getMatrixDimensions($operand2);
+ if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
+ $resize = 1;
+ }
+
+ if ($resize == 2) {
+ // Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
+ self::_resizeMatricesExtend($operand1,$operand2,$matrix1Rows,$matrix1Columns,$matrix2Rows,$matrix2Columns);
+ } elseif ($resize == 1) {
+ // Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
+ self::_resizeMatricesShrink($operand1,$operand2,$matrix1Rows,$matrix1Columns,$matrix2Rows,$matrix2Columns);
+ }
+ return array( $matrix1Rows,$matrix1Columns,$matrix2Rows,$matrix2Columns);
+ } // function _checkMatrixOperands()
+
+
+ /**
+ * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0
+ *
+ * @param mixed &$matrix matrix operand
+ * @return array An array comprising the number of rows, and number of columns
+ */
+ public static function _getMatrixDimensions(&$matrix) {
+ $matrixRows = count($matrix);
+ $matrixColumns = 0;
+ foreach($matrix as $rowKey => $rowValue) {
+ $matrixColumns = max(count($rowValue),$matrixColumns);
+ if (!is_array($rowValue)) {
+ $matrix[$rowKey] = array($rowValue);
+ } else {
+ $matrix[$rowKey] = array_values($rowValue);
+ }
+ }
+ $matrix = array_values($matrix);
+ return array($matrixRows,$matrixColumns);
+ } // function _getMatrixDimensions()
+
+
+ /**
+ * Ensure that paired matrix operands are both matrices of the same size
+ *
+ * @param mixed &$matrix1 First matrix operand
+ * @param mixed &$matrix2 Second matrix operand
+ * @param integer $matrix1Rows Row size of first matrix operand
+ * @param integer $matrix1Columns Column size of first matrix operand
+ * @param integer $matrix2Rows Row size of second matrix operand
+ * @param integer $matrix2Columns Column size of second matrix operand
+ */
+ private static function _resizeMatricesShrink(&$matrix1,&$matrix2,$matrix1Rows,$matrix1Columns,$matrix2Rows,$matrix2Columns) {
+ if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
+ if ($matrix2Rows < $matrix1Rows) {
+ for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
+ unset($matrix1[$i]);
+ }
+ }
+ if ($matrix2Columns < $matrix1Columns) {
+ for ($i = 0; $i < $matrix1Rows; ++$i) {
+ for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
+ unset($matrix1[$i][$j]);
+ }
+ }
+ }
+ }
+
+ if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
+ if ($matrix1Rows < $matrix2Rows) {
+ for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
+ unset($matrix2[$i]);
+ }
+ }
+ if ($matrix1Columns < $matrix2Columns) {
+ for ($i = 0; $i < $matrix2Rows; ++$i) {
+ for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
+ unset($matrix2[$i][$j]);
+ }
+ }
+ }
+ }
+ } // function _resizeMatricesShrink()
+
+
+ /**
+ * Ensure that paired matrix operands are both matrices of the same size
+ *
+ * @param mixed &$matrix1 First matrix operand
+ * @param mixed &$matrix2 Second matrix operand
+ * @param integer $matrix1Rows Row size of first matrix operand
+ * @param integer $matrix1Columns Column size of first matrix operand
+ * @param integer $matrix2Rows Row size of second matrix operand
+ * @param integer $matrix2Columns Column size of second matrix operand
+ */
+ private static function _resizeMatricesExtend(&$matrix1,&$matrix2,$matrix1Rows,$matrix1Columns,$matrix2Rows,$matrix2Columns) {
+ if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
+ if ($matrix2Columns < $matrix1Columns) {
+ for ($i = 0; $i < $matrix2Rows; ++$i) {
+ $x = $matrix2[$i][$matrix2Columns-1];
+ for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
+ $matrix2[$i][$j] = $x;
+ }
+ }
+ }
+ if ($matrix2Rows < $matrix1Rows) {
+ $x = $matrix2[$matrix2Rows-1];
+ for ($i = 0; $i < $matrix1Rows; ++$i) {
+ $matrix2[$i] = $x;
+ }
+ }
+ }
+
+ if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
+ if ($matrix1Columns < $matrix2Columns) {
+ for ($i = 0; $i < $matrix1Rows; ++$i) {
+ $x = $matrix1[$i][$matrix1Columns-1];
+ for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
+ $matrix1[$i][$j] = $x;
+ }
+ }
+ }
+ if ($matrix1Rows < $matrix2Rows) {
+ $x = $matrix1[$matrix1Rows-1];
+ for ($i = 0; $i < $matrix2Rows; ++$i) {
+ $matrix1[$i] = $x;
+ }
+ }
+ }
+ } // function _resizeMatricesExtend()
+
+
+ /**
+ * Format details of an operand for display in the log (based on operand type)
+ *
+ * @param mixed $value First matrix operand
+ * @return mixed
+ */
+ private function _showValue($value) {
+ if ($this->_debugLog->getWriteDebugLog()) {
+ $testArray = PHPExcel_Calculation_Functions::flattenArray($value);
+ if (count($testArray) == 1) {
+ $value = array_pop($testArray);
+ }
+
+ if (is_array($value)) {
+ $returnMatrix = array();
+ $pad = $rpad = ', ';
+ foreach($value as $row) {
+ if (is_array($row)) {
+ $returnMatrix[] = implode($pad,array_map(array($this,'_showValue'),$row));
+ $rpad = '; ';
+ } else {
+ $returnMatrix[] = $this->_showValue($row);
+ }
+ }
+ return '{ '.implode($rpad,$returnMatrix).' }';
+ } elseif(is_string($value) && (trim($value,'"') == $value)) {
+ return '"'.$value.'"';
+ } elseif(is_bool($value)) {
+ return ($value) ? self::$_localeBoolean['TRUE'] : self::$_localeBoolean['FALSE'];
+ }
+ }
+ return PHPExcel_Calculation_Functions::flattenSingleValue($value);
+ } // function _showValue()
+
+
+ /**
+ * Format type and details of an operand for display in the log (based on operand type)
+ *
+ * @param mixed $value First matrix operand
+ * @return mixed
+ */
+ private function _showTypeDetails($value) {
+ if ($this->_debugLog->getWriteDebugLog()) {
+ $testArray = PHPExcel_Calculation_Functions::flattenArray($value);
+ if (count($testArray) == 1) {
+ $value = array_pop($testArray);
+ }
+
+ if ($value === NULL) {
+ return 'a NULL value';
+ } elseif (is_float($value)) {
+ $typeString = 'a floating point number';
+ } elseif(is_int($value)) {
+ $typeString = 'an integer number';
+ } elseif(is_bool($value)) {
+ $typeString = 'a boolean';
+ } elseif(is_array($value)) {
+ $typeString = 'a matrix';
+ } else {
+ if ($value == '') {
+ return 'an empty string';
+ } elseif ($value{0} == '#') {
+ return 'a '.$value.' error';
+ } else {
+ $typeString = 'a string';
+ }
+ }
+ return $typeString.' with a value of '.$this->_showValue($value);
+ }
+ } // function _showTypeDetails()
+
+
+ private static function _convertMatrixReferences($formula) {
+ static $matrixReplaceFrom = array('{',';','}');
+ static $matrixReplaceTo = array('MKMATRIX(MKMATRIX(','),MKMATRIX(','))');
+
+ // Convert any Excel matrix references to the MKMATRIX() function
+ if (strpos($formula,'{') !== FALSE) {
+ // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
+ if (strpos($formula,'"') !== FALSE) {
+ // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
+ // the formula
+ $temp = explode('"',$formula);
+ // Open and Closed counts used for trapping mismatched braces in the formula
+ $openCount = $closeCount = 0;
+ $i = FALSE;
+ foreach($temp as &$value) {
+ // Only count/replace in alternating array entries
+ if ($i = !$i) {
+ $openCount += substr_count($value,'{');
+ $closeCount += substr_count($value,'}');
+ $value = str_replace($matrixReplaceFrom,$matrixReplaceTo,$value);
+ }
+ }
+ unset($value);
+ // Then rebuild the formula string
+ $formula = implode('"',$temp);
+ } else {
+ // If there's no quoted strings, then we do a simple count/replace
+ $openCount = substr_count($formula,'{');
+ $closeCount = substr_count($formula,'}');
+ $formula = str_replace($matrixReplaceFrom,$matrixReplaceTo,$formula);
+ }
+ // Trap for mismatched braces and trigger an appropriate error
+ if ($openCount < $closeCount) {
+ if ($openCount > 0) {
+ return $this->_raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
+ } else {
+ return $this->_raiseFormulaError("Formula Error: Unexpected '}' encountered");
+ }
+ } elseif ($openCount > $closeCount) {
+ if ($closeCount > 0) {
+ return $this->_raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
+ } else {
+ return $this->_raiseFormulaError("Formula Error: Unexpected '{' encountered");
+ }
+ }
+ }
+
+ return $formula;
+ } // function _convertMatrixReferences()
+
+
+ private static function _mkMatrix() {
+ return func_get_args();
+ } // function _mkMatrix()
+
+
+ // Binary Operators
+ // These operators always work on two values
+ // Array key is the operator, the value indicates whether this is a left or right associative operator
+ private static $_operatorAssociativity = array(
+ '^' => 0, // Exponentiation
+ '*' => 0, '/' => 0, // Multiplication and Division
+ '+' => 0, '-' => 0, // Addition and Subtraction
+ '&' => 0, // Concatenation
+ '|' => 0, ':' => 0, // Intersect and Range
+ '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0 // Comparison
+ );
+
+ // Comparison (Boolean) Operators
+ // These operators work on two values, but always return a boolean result
+ private static $_comparisonOperators = array('>' => TRUE, '<' => TRUE, '=' => TRUE, '>=' => TRUE, '<=' => TRUE, '<>' => TRUE);
+
+ // Operator Precedence
+ // This list includes all valid operators, whether binary (including boolean) or unary (such as %)
+ // Array key is the operator, the value is its precedence
+ private static $_operatorPrecedence = array(
+ ':' => 8, // Range
+ '|' => 7, // Intersect
+ '~' => 6, // Negation
+ '%' => 5, // Percentage
+ '^' => 4, // Exponentiation
+ '*' => 3, '/' => 3, // Multiplication and Division
+ '+' => 2, '-' => 2, // Addition and Subtraction
+ '&' => 1, // Concatenation
+ '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0 // Comparison
+ );
+
+ // Convert infix to postfix notation
+ private function _parseFormula($formula, PHPExcel_Cell $pCell = NULL) {
+ if (($formula = self::_convertMatrixReferences(trim($formula))) === FALSE) {
+ return FALSE;
+ }
+
+ // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
+ // so we store the parent worksheet so that we can re-attach it when necessary
+ $pCellParent = ($pCell !== NULL) ? $pCell->getWorksheet() : NULL;
+
+ $regexpMatchString = '/^('.self::CALCULATION_REGEXP_FUNCTION.
+ '|'.self::CALCULATION_REGEXP_NUMBER.
+ '|'.self::CALCULATION_REGEXP_STRING.
+ '|'.self::CALCULATION_REGEXP_OPENBRACE.
+ '|'.self::CALCULATION_REGEXP_CELLREF.
+ '|'.self::CALCULATION_REGEXP_NAMEDRANGE.
+ '|'.self::CALCULATION_REGEXP_ERROR.
+ ')/si';
+
+ // Start with initialisation
+ $index = 0;
+ $stack = new PHPExcel_Calculation_Token_Stack;
+ $output = array();
+ $expectingOperator = FALSE; // We use this test in syntax-checking the expression to determine when a
+ // - is a negation or + is a positive operator rather than an operation
+ $expectingOperand = FALSE; // We use this test in syntax-checking the expression to determine whether an operand
+ // should be null in a function call
+ // The guts of the lexical parser
+ // Loop through the formula extracting each operator and operand in turn
+ while(TRUE) {
+//echo 'Assessing Expression '.substr($formula, $index),PHP_EOL;
+ $opCharacter = $formula{$index}; // Get the first character of the value at the current index position
+//echo 'Initial character of expression block is '.$opCharacter,PHP_EOL;
+ if ((isset(self::$_comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$_comparisonOperators[$formula{$index+1}]))) {
+ $opCharacter .= $formula{++$index};
+//echo 'Initial character of expression block is comparison operator '.$opCharacter.PHP_EOL;
+ }
+
+ // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
+ $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
+//echo '$isOperandOrFunction is '.(($isOperandOrFunction) ? 'True' : 'False').PHP_EOL;
+//var_dump($match);
+
+ if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus?
+//echo 'Element is a Negation operator',PHP_EOL;
+ $stack->push('Unary Operator','~'); // Put a negation on the stack
+ ++$index; // and drop the negation symbol
+ } elseif ($opCharacter == '%' && $expectingOperator) {
+//echo 'Element is a Percentage operator',PHP_EOL;
+ $stack->push('Unary Operator','%'); // Put a percentage on the stack
+ ++$index;
+ } elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
+//echo 'Element is a Positive number, not Plus operator',PHP_EOL;
+ ++$index; // Drop the redundant plus symbol
+ } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
+ return $this->_raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
+
+ } elseif ((isset(self::$_operators[$opCharacter]) or $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
+//echo 'Element with value '.$opCharacter.' is an Operator',PHP_EOL;
+ while($stack->count() > 0 &&
+ ($o2 = $stack->last()) &&
+ isset(self::$_operators[$o2['value']]) &&
+ @(self::$_operatorAssociativity[$opCharacter] ? self::$_operatorPrecedence[$opCharacter] < self::$_operatorPrecedence[$o2['value']] : self::$_operatorPrecedence[$opCharacter] <= self::$_operatorPrecedence[$o2['value']])) {
+ $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
+ }
+ $stack->push('Binary Operator',$opCharacter); // Finally put our current operator onto the stack
+ ++$index;
+ $expectingOperator = FALSE;
+
+ } elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
+//echo 'Element is a Closing bracket',PHP_EOL;
+ $expectingOperand = FALSE;
+ while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
+ if ($o2 === NULL) return $this->_raiseFormulaError('Formula Error: Unexpected closing brace ")"');
+ else $output[] = $o2;
+ }
+ $d = $stack->last(2);
+ if (preg_match('/^'.self::CALCULATION_REGEXP_FUNCTION.'$/i', $d['value'], $matches)) { // Did this parenthesis just close a function?
+ $functionName = $matches[1]; // Get the function name
+//echo 'Closed Function is '.$functionName,PHP_EOL;
+ $d = $stack->pop();
+ $argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack)
+//if ($argumentCount == 0) {
+// echo 'With no arguments',PHP_EOL;
+//} elseif ($argumentCount == 1) {
+// echo 'With 1 argument',PHP_EOL;
+//} else {
+// echo 'With '.$argumentCount.' arguments',PHP_EOL;
+//}
+ $output[] = $d; // Dump the argument count on the output
+ $output[] = $stack->pop(); // Pop the function and push onto the output
+ if (isset(self::$_controlFunctions[$functionName])) {
+//echo 'Built-in function '.$functionName,PHP_EOL;
+ $expectedArgumentCount = self::$_controlFunctions[$functionName]['argumentCount'];
+ $functionCall = self::$_controlFunctions[$functionName]['functionCall'];
+ } elseif (isset(self::$_PHPExcelFunctions[$functionName])) {
+//echo 'PHPExcel function '.$functionName,PHP_EOL;
+ $expectedArgumentCount = self::$_PHPExcelFunctions[$functionName]['argumentCount'];
+ $functionCall = self::$_PHPExcelFunctions[$functionName]['functionCall'];
+ } else { // did we somehow push a non-function on the stack? this should never happen
+ return $this->_raiseFormulaError("Formula Error: Internal error, non-function on stack");
+ }
+ // Check the argument count
+ $argumentCountError = FALSE;
+ if (is_numeric($expectedArgumentCount)) {
+ if ($expectedArgumentCount < 0) {
+//echo '$expectedArgumentCount is between 0 and '.abs($expectedArgumentCount),PHP_EOL;
+ if ($argumentCount > abs($expectedArgumentCount)) {
+ $argumentCountError = TRUE;
+ $expectedArgumentCountString = 'no more than '.abs($expectedArgumentCount);
+ }
+ } else {
+//echo '$expectedArgumentCount is numeric '.$expectedArgumentCount,PHP_EOL;
+ if ($argumentCount != $expectedArgumentCount) {
+ $argumentCountError = TRUE;
+ $expectedArgumentCountString = $expectedArgumentCount;
+ }
+ }
+ } elseif ($expectedArgumentCount != '*') {
+ $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/',$expectedArgumentCount,$argMatch);
+//print_r($argMatch);
+//echo PHP_EOL;
+ switch ($argMatch[2]) {
+ case '+' :
+ if ($argumentCount < $argMatch[1]) {
+ $argumentCountError = TRUE;
+ $expectedArgumentCountString = $argMatch[1].' or more ';
+ }
+ break;
+ case '-' :
+ if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
+ $argumentCountError = TRUE;
+ $expectedArgumentCountString = 'between '.$argMatch[1].' and '.$argMatch[3];
+ }
+ break;
+ case ',' :
+ if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
+ $argumentCountError = TRUE;
+ $expectedArgumentCountString = 'either '.$argMatch[1].' or '.$argMatch[3];
+ }
+ break;
+ }
+ }
+ if ($argumentCountError) {
+ return $this->_raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, ".$expectedArgumentCountString." expected");
+ }
+ }
+ ++$index;
+
+ } elseif ($opCharacter == ',') { // Is this the separator for function arguments?
+//echo 'Element is a Function argument separator',PHP_EOL;
+ while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
+ if ($o2 === NULL) return $this->_raiseFormulaError("Formula Error: Unexpected ,");
+ else $output[] = $o2; // pop the argument expression stuff and push onto the output
+ }
+ // If we've a comma when we're expecting an operand, then what we actually have is a null operand;
+ // so push a null onto the stack
+ if (($expectingOperand) || (!$expectingOperator)) {
+ $output[] = array('type' => 'NULL Value', 'value' => self::$_ExcelConstants['NULL'], 'reference' => NULL);
+ }
+ // make sure there was a function
+ $d = $stack->last(2);
+ if (!preg_match('/^'.self::CALCULATION_REGEXP_FUNCTION.'$/i', $d['value'], $matches))
+ return $this->_raiseFormulaError("Formula Error: Unexpected ,");
+ $d = $stack->pop();
+ $stack->push($d['type'],++$d['value'],$d['reference']); // increment the argument count
+ $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
+ $expectingOperator = FALSE;
+ $expectingOperand = TRUE;
+ ++$index;
+
+ } elseif ($opCharacter == '(' && !$expectingOperator) {
+// echo 'Element is an Opening Bracket
';
+ $stack->push('Brace', '(');
+ ++$index;
+
+ } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number?
+ $expectingOperator = TRUE;
+ $expectingOperand = FALSE;
+ $val = $match[1];
+ $length = strlen($val);
+// echo 'Element with value '.$val.' is an Operand, Variable, Constant, String, Number, Cell Reference or Function
';
+
+ if (preg_match('/^'.self::CALCULATION_REGEXP_FUNCTION.'$/i', $val, $matches)) {
+ $val = preg_replace('/\s/','',$val);
+// echo 'Element '.$val.' is a Function
';
+ if (isset(self::$_PHPExcelFunctions[strtoupper($matches[1])]) || isset(self::$_controlFunctions[strtoupper($matches[1])])) { // it's a function
+ $stack->push('Function', strtoupper($val));
+ $ax = preg_match('/^\s*(\s*\))/i', substr($formula, $index+$length), $amatch);
+ if ($ax) {
+ $stack->push('Operand Count for Function '.strtoupper($val).')', 0);
+ $expectingOperator = TRUE;
+ } else {
+ $stack->push('Operand Count for Function '.strtoupper($val).')', 1);
+ $expectingOperator = FALSE;
+ }
+ $stack->push('Brace', '(');
+ } else { // it's a var w/ implicit multiplication
+ $output[] = array('type' => 'Value', 'value' => $matches[1], 'reference' => NULL);
+ }
+ } elseif (preg_match('/^'.self::CALCULATION_REGEXP_CELLREF.'$/i', $val, $matches)) {
+// echo 'Element '.$val.' is a Cell reference
';
+ // Watch for this case-change when modifying to allow cell references in different worksheets...
+ // Should only be applied to the actual cell column, not the worksheet name
+
+ // If the last entry on the stack was a : operator, then we have a cell range reference
+ $testPrevOp = $stack->last(1);
+ if ($testPrevOp['value'] == ':') {
+ // If we have a worksheet reference, then we're playing with a 3D reference
+ if ($matches[2] == '') {
+ // Otherwise, we 'inherit' the worksheet reference from the start cell reference
+ // The start of the cell range reference should be the last entry in $output
+ $startCellRef = $output[count($output)-1]['value'];
+ preg_match('/^'.self::CALCULATION_REGEXP_CELLREF.'$/i', $startCellRef, $startMatches);
+ if ($startMatches[2] > '') {
+ $val = $startMatches[2].'!'.$val;
+ }
+ } else {
+ return $this->_raiseFormulaError("3D Range references are not yet supported");
+ }
+ }
+
+ $output[] = array('type' => 'Cell Reference', 'value' => $val, 'reference' => $val);
+// $expectingOperator = FALSE;
+ } else { // it's a variable, constant, string, number or boolean
+// echo 'Element is a Variable, Constant, String, Number or Boolean
';
+ // If the last entry on the stack was a : operator, then we may have a row or column range reference
+ $testPrevOp = $stack->last(1);
+ if ($testPrevOp['value'] == ':') {
+ $startRowColRef = $output[count($output)-1]['value'];
+ $rangeWS1 = '';
+ if (strpos('!',$startRowColRef) !== FALSE) {
+ list($rangeWS1,$startRowColRef) = explode('!',$startRowColRef);
+ }
+ if ($rangeWS1 != '') $rangeWS1 .= '!';
+ $rangeWS2 = $rangeWS1;
+ if (strpos('!',$val) !== FALSE) {
+ list($rangeWS2,$val) = explode('!',$val);
+ }
+ if ($rangeWS2 != '') $rangeWS2 .= '!';
+ if ((is_integer($startRowColRef)) && (ctype_digit($val)) &&
+ ($startRowColRef <= 1048576) && ($val <= 1048576)) {
+ // Row range
+ $endRowColRef = ($pCellParent !== NULL) ? $pCellParent->getHighestColumn() : 'XFD'; // Max 16,384 columns for Excel2007
+ $output[count($output)-1]['value'] = $rangeWS1.'A'.$startRowColRef;
+ $val = $rangeWS2.$endRowColRef.$val;
+ } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
+ (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
+ // Column range
+ $endRowColRef = ($pCellParent !== NULL) ? $pCellParent->getHighestRow() : 1048576; // Max 1,048,576 rows for Excel2007
+ $output[count($output)-1]['value'] = $rangeWS1.strtoupper($startRowColRef).'1';
+ $val = $rangeWS2.$val.$endRowColRef;
+ }
+ }
+
+ $localeConstant = FALSE;
+ if ($opCharacter == '"') {
+// echo 'Element is a String
';
+ // UnEscape any quotes within the string
+ $val = self::_wrapResult(str_replace('""','"',self::_unwrapResult($val)));
+ } elseif (is_numeric($val)) {
+// echo 'Element is a Number
';
+ if ((strpos($val,'.') !== FALSE) || (stripos($val,'e') !== FALSE) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
+// echo 'Casting '.$val.' to float
';
+ $val = (float) $val;
+ } else {
+// echo 'Casting '.$val.' to integer
';
+ $val = (integer) $val;
+ }
+ } elseif (isset(self::$_ExcelConstants[trim(strtoupper($val))])) {
+ $excelConstant = trim(strtoupper($val));
+// echo 'Element '.$excelConstant.' is an Excel Constant
';
+ $val = self::$_ExcelConstants[$excelConstant];
+ } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$_localeBoolean)) !== FALSE) {
+// echo 'Element '.$localeConstant.' is an Excel Constant
';
+ $val = self::$_ExcelConstants[$localeConstant];
+ }
+ $details = array('type' => 'Value', 'value' => $val, 'reference' => NULL);
+ if ($localeConstant) { $details['localeValue'] = $localeConstant; }
+ $output[] = $details;
+ }
+ $index += $length;
+
+ } elseif ($opCharacter == '$') { // absolute row or column range
+ ++$index;
+ } elseif ($opCharacter == ')') { // miscellaneous error checking
+ if ($expectingOperand) {
+ $output[] = array('type' => 'NULL Value', 'value' => self::$_ExcelConstants['NULL'], 'reference' => NULL);
+ $expectingOperand = FALSE;
+ $expectingOperator = TRUE;
+ } else {
+ return $this->_raiseFormulaError("Formula Error: Unexpected ')'");
+ }
+ } elseif (isset(self::$_operators[$opCharacter]) && !$expectingOperator) {
+ return $this->_raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
+ } else { // I don't even want to know what you did to get here
+ return $this->_raiseFormulaError("Formula Error: An unexpected error occured");
+ }
+ // Test for end of formula string
+ if ($index == strlen($formula)) {
+ // Did we end with an operator?.
+ // Only valid for the % unary operator
+ if ((isset(self::$_operators[$opCharacter])) && ($opCharacter != '%')) {
+ return $this->_raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
+ } else {
+ break;
+ }
+ }
+ // Ignore white space
+ while (($formula{$index} == "\n") || ($formula{$index} == "\r")) {
+ ++$index;
+ }
+ if ($formula{$index} == ' ') {
+ while ($formula{$index} == ' ') {
+ ++$index;
+ }
+ // If we're expecting an operator, but only have a space between the previous and next operands (and both are
+ // Cell References) then we have an INTERSECTION operator
+// echo 'Possible Intersect Operator
';
+ if (($expectingOperator) && (preg_match('/^'.self::CALCULATION_REGEXP_CELLREF.'.*/Ui', substr($formula, $index), $match)) &&
+ ($output[count($output)-1]['type'] == 'Cell Reference')) {
+// echo 'Element is an Intersect Operator
';
+ while($stack->count() > 0 &&
+ ($o2 = $stack->last()) &&
+ isset(self::$_operators[$o2['value']]) &&
+ @(self::$_operatorAssociativity[$opCharacter] ? self::$_operatorPrecedence[$opCharacter] < self::$_operatorPrecedence[$o2['value']] : self::$_operatorPrecedence[$opCharacter] <= self::$_operatorPrecedence[$o2['value']])) {
+ $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
+ }
+ $stack->push('Binary Operator','|'); // Put an Intersect Operator on the stack
+ $expectingOperator = FALSE;
+ }
+ }
+ }
+
+ while (($op = $stack->pop()) !== NULL) { // pop everything off the stack and push onto output
+ if ((is_array($op) && $op['value'] == '(') || ($op === '('))
+ return $this->_raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
+ $output[] = $op;
+ }
+ return $output;
+ } // function _parseFormula()
+
+
+ private static function _dataTestReference(&$operandData)
+ {
+ $operand = $operandData['value'];
+ if (($operandData['reference'] === NULL) && (is_array($operand))) {
+ $rKeys = array_keys($operand);
+ $rowKey = array_shift($rKeys);
+ $cKeys = array_keys(array_keys($operand[$rowKey]));
+ $colKey = array_shift($cKeys);
+ if (ctype_upper($colKey)) {
+ $operandData['reference'] = $colKey.$rowKey;
+ }
+ }
+ return $operand;
+ }
+
+ // evaluate postfix notation
+ private function _processTokenStack($tokens, $cellID = NULL, PHPExcel_Cell $pCell = NULL) {
+ if ($tokens == FALSE) return FALSE;
+
+ // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
+ // so we store the parent cell collection so that we can re-attach it when necessary
+ $pCellWorksheet = ($pCell !== NULL) ? $pCell->getWorksheet() : NULL;
+ $pCellParent = ($pCell !== NULL) ? $pCell->getParent() : null;
+ $stack = new PHPExcel_Calculation_Token_Stack;
+
+ // Loop through each token in turn
+ foreach ($tokens as $tokenData) {
+// print_r($tokenData);
+// echo '
';
+ $token = $tokenData['value'];
+// echo 'Token is '.$token.'
';
+ // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
+ if (isset(self::$_binaryOperators[$token])) {
+// echo 'Token is a binary operator
';
+ // We must have two operands, error if we don't
+ if (($operand2Data = $stack->pop()) === NULL) return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
+ if (($operand1Data = $stack->pop()) === NULL) return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
+
+ $operand1 = self::_dataTestReference($operand1Data);
+ $operand2 = self::_dataTestReference($operand2Data);
+
+ // Log what we're doing
+ if ($token == ':') {
+ $this->_debugLog->writeDebugLog('Evaluating Range ', $this->_showValue($operand1Data['reference']), ' ', $token, ' ', $this->_showValue($operand2Data['reference']));
+ } else {
+ $this->_debugLog->writeDebugLog('Evaluating ', $this->_showValue($operand1), ' ', $token, ' ', $this->_showValue($operand2));
+ }
+
+ // Process the operation in the appropriate manner
+ switch ($token) {
+ // Comparison (Boolean) Operators
+ case '>' : // Greater than
+ case '<' : // Less than
+ case '>=' : // Greater than or Equal to
+ case '<=' : // Less than or Equal to
+ case '=' : // Equality
+ case '<>' : // Inequality
+ $this->_executeBinaryComparisonOperation($cellID,$operand1,$operand2,$token,$stack);
+ break;
+ // Binary Operators
+ case ':' : // Range
+ $sheet1 = $sheet2 = '';
+ if (strpos($operand1Data['reference'],'!') !== FALSE) {
+ list($sheet1,$operand1Data['reference']) = explode('!',$operand1Data['reference']);
+ } else {
+ $sheet1 = ($pCellParent !== NULL) ? $pCellWorksheet->getTitle() : '';
+ }
+ if (strpos($operand2Data['reference'],'!') !== FALSE) {
+ list($sheet2,$operand2Data['reference']) = explode('!',$operand2Data['reference']);
+ } else {
+ $sheet2 = $sheet1;
+ }
+ if ($sheet1 == $sheet2) {
+ if ($operand1Data['reference'] === NULL) {
+ if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
+ $operand1Data['reference'] = $pCell->getColumn().$operand1Data['value'];
+ } elseif (trim($operand1Data['reference']) == '') {
+ $operand1Data['reference'] = $pCell->getCoordinate();
+ } else {
+ $operand1Data['reference'] = $operand1Data['value'].$pCell->getRow();
+ }
+ }
+ if ($operand2Data['reference'] === NULL) {
+ if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
+ $operand2Data['reference'] = $pCell->getColumn().$operand2Data['value'];
+ } elseif (trim($operand2Data['reference']) == '') {
+ $operand2Data['reference'] = $pCell->getCoordinate();
+ } else {
+ $operand2Data['reference'] = $operand2Data['value'].$pCell->getRow();
+ }
+ }
+
+ $oData = array_merge(explode(':',$operand1Data['reference']),explode(':',$operand2Data['reference']));
+ $oCol = $oRow = array();
+ foreach($oData as $oDatum) {
+ $oCR = PHPExcel_Cell::coordinateFromString($oDatum);
+ $oCol[] = PHPExcel_Cell::columnIndexFromString($oCR[0]) - 1;
+ $oRow[] = $oCR[1];
+ }
+ $cellRef = PHPExcel_Cell::stringFromColumnIndex(min($oCol)).min($oRow).':'.PHPExcel_Cell::stringFromColumnIndex(max($oCol)).max($oRow);
+ if ($pCellParent !== NULL) {
+ $cellValue = $this->extractCellRange($cellRef, $this->_workbook->getSheetByName($sheet1), FALSE);
+ } else {
+ return $this->_raiseFormulaError('Unable to access Cell Reference');
+ }
+ $stack->push('Cell Reference',$cellValue,$cellRef);
+ } else {
+ $stack->push('Error',PHPExcel_Calculation_Functions::REF(),NULL);
+ }
+
+ break;
+ case '+' : // Addition
+ $this->_executeNumericBinaryOperation($cellID,$operand1,$operand2,$token,'plusEquals',$stack);
+ break;
+ case '-' : // Subtraction
+ $this->_executeNumericBinaryOperation($cellID,$operand1,$operand2,$token,'minusEquals',$stack);
+ break;
+ case '*' : // Multiplication
+ $this->_executeNumericBinaryOperation($cellID,$operand1,$operand2,$token,'arrayTimesEquals',$stack);
+ break;
+ case '/' : // Division
+ $this->_executeNumericBinaryOperation($cellID,$operand1,$operand2,$token,'arrayRightDivide',$stack);
+ break;
+ case '^' : // Exponential
+ $this->_executeNumericBinaryOperation($cellID,$operand1,$operand2,$token,'power',$stack);
+ break;
+ case '&' : // Concatenation
+ // If either of the operands is a matrix, we need to treat them both as matrices
+ // (converting the other operand to a matrix if need be); then perform the required
+ // matrix operation
+ if (is_bool($operand1)) {
+ $operand1 = ($operand1) ? self::$_localeBoolean['TRUE'] : self::$_localeBoolean['FALSE'];
+ }
+ if (is_bool($operand2)) {
+ $operand2 = ($operand2) ? self::$_localeBoolean['TRUE'] : self::$_localeBoolean['FALSE'];
+ }
+ if ((is_array($operand1)) || (is_array($operand2))) {
+ // Ensure that both operands are arrays/matrices
+ self::_checkMatrixOperands($operand1,$operand2,2);
+ try {
+ // Convert operand 1 from a PHP array to a matrix
+ $matrix = new PHPExcel_Shared_JAMA_Matrix($operand1);
+ // Perform the required operation against the operand 1 matrix, passing in operand 2
+ $matrixResult = $matrix->concat($operand2);
+ $result = $matrixResult->getArray();
+ } catch (PHPExcel_Exception $ex) {
+ $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
+ $result = '#VALUE!';
+ }
+ } else {
+ $result = '"'.str_replace('""','"',self::_unwrapResult($operand1,'"').self::_unwrapResult($operand2,'"')).'"';
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($result));
+ $stack->push('Value',$result);
+ break;
+ case '|' : // Intersect
+ $rowIntersect = array_intersect_key($operand1,$operand2);
+ $cellIntersect = $oCol = $oRow = array();
+ foreach(array_keys($rowIntersect) as $row) {
+ $oRow[] = $row;
+ foreach($rowIntersect[$row] as $col => $data) {
+ $oCol[] = PHPExcel_Cell::columnIndexFromString($col) - 1;
+ $cellIntersect[$row] = array_intersect_key($operand1[$row],$operand2[$row]);
+ }
+ }
+ $cellRef = PHPExcel_Cell::stringFromColumnIndex(min($oCol)).min($oRow).':'.PHPExcel_Cell::stringFromColumnIndex(max($oCol)).max($oRow);
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($cellIntersect));
+ $stack->push('Value',$cellIntersect,$cellRef);
+ break;
+ }
+
+ // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
+ } elseif (($token === '~') || ($token === '%')) {
+// echo 'Token is a unary operator
';
+ if (($arg = $stack->pop()) === NULL) return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
+ $arg = $arg['value'];
+ if ($token === '~') {
+// echo 'Token is a negation operator
';
+ $this->_debugLog->writeDebugLog('Evaluating Negation of ', $this->_showValue($arg));
+ $multiplier = -1;
+ } else {
+// echo 'Token is a percentile operator
';
+ $this->_debugLog->writeDebugLog('Evaluating Percentile of ', $this->_showValue($arg));
+ $multiplier = 0.01;
+ }
+ if (is_array($arg)) {
+ self::_checkMatrixOperands($arg,$multiplier,2);
+ try {
+ $matrix1 = new PHPExcel_Shared_JAMA_Matrix($arg);
+ $matrixResult = $matrix1->arrayTimesEquals($multiplier);
+ $result = $matrixResult->getArray();
+ } catch (PHPExcel_Exception $ex) {
+ $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
+ $result = '#VALUE!';
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($result));
+ $stack->push('Value',$result);
+ } else {
+ $this->_executeNumericBinaryOperation($cellID,$multiplier,$arg,'*','arrayTimesEquals',$stack);
+ }
+
+ } elseif (preg_match('/^'.self::CALCULATION_REGEXP_CELLREF.'$/i', $token, $matches)) {
+ $cellRef = NULL;
+// echo 'Element '.$token.' is a Cell reference
';
+ if (isset($matches[8])) {
+// echo 'Reference is a Range of cells
';
+ if ($pCell === NULL) {
+// We can't access the range, so return a REF error
+ $cellValue = PHPExcel_Calculation_Functions::REF();
+ } else {
+ $cellRef = $matches[6].$matches[7].':'.$matches[9].$matches[10];
+ if ($matches[2] > '') {
+ $matches[2] = trim($matches[2],"\"'");
+ if ((strpos($matches[2],'[') !== FALSE) || (strpos($matches[2],']') !== FALSE)) {
+ // It's a Reference to an external workbook (not currently supported)
+ return $this->_raiseFormulaError('Unable to access External Workbook');
+ }
+ $matches[2] = trim($matches[2],"\"'");
+// echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'
';
+ $this->_debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
+ if ($pCellParent !== NULL) {
+ $cellValue = $this->extractCellRange($cellRef, $this->_workbook->getSheetByName($matches[2]), FALSE);
+ } else {
+ return $this->_raiseFormulaError('Unable to access Cell Reference');
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->_showTypeDetails($cellValue));
+// $cellRef = $matches[2].'!'.$cellRef;
+ } else {
+// echo '$cellRef='.$cellRef.' in current worksheet
';
+ $this->_debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
+ if ($pCellParent !== NULL) {
+ $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, FALSE);
+ } else {
+ return $this->_raiseFormulaError('Unable to access Cell Reference');
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->_showTypeDetails($cellValue));
+ }
+ }
+ } else {
+// echo 'Reference is a single Cell
';
+ if ($pCell === NULL) {
+// We can't access the cell, so return a REF error
+ $cellValue = PHPExcel_Calculation_Functions::REF();
+ } else {
+ $cellRef = $matches[6].$matches[7];
+ if ($matches[2] > '') {
+ $matches[2] = trim($matches[2],"\"'");
+ if ((strpos($matches[2],'[') !== FALSE) || (strpos($matches[2],']') !== FALSE)) {
+ // It's a Reference to an external workbook (not currently supported)
+ return $this->_raiseFormulaError('Unable to access External Workbook');
+ }
+// echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'
';
+ $this->_debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
+ if ($pCellParent !== NULL) {
+ if ($this->_workbook->getSheetByName($matches[2])->cellExists($cellRef)) {
+ $cellValue = $this->extractCellRange($cellRef, $this->_workbook->getSheetByName($matches[2]), FALSE);
+ $pCell->attach($pCellParent);
+ } else {
+ $cellValue = NULL;
+ }
+ } else {
+ return $this->_raiseFormulaError('Unable to access Cell Reference');
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->_showTypeDetails($cellValue));
+// $cellRef = $matches[2].'!'.$cellRef;
+ } else {
+// echo '$cellRef='.$cellRef.' in current worksheet
';
+ $this->_debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
+ if ($pCellParent->isDataSet($cellRef)) {
+ $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, FALSE);
+ $pCell->attach($pCellParent);
+ } else {
+ $cellValue = NULL;
+ }
+ $this->_debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->_showTypeDetails($cellValue));
+ }
+ }
+ }
+ $stack->push('Value',$cellValue,$cellRef);
+
+ // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
+ } elseif (preg_match('/^'.self::CALCULATION_REGEXP_FUNCTION.'$/i', $token, $matches)) {
+// echo 'Token is a function
';
+ $functionName = $matches[1];
+ $argCount = $stack->pop();
+ $argCount = $argCount['value'];
+ if ($functionName != 'MKMATRIX') {
+ $this->_debugLog->writeDebugLog('Evaluating Function ', self::_localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
+ }
+ if ((isset(self::$_PHPExcelFunctions[$functionName])) || (isset(self::$_controlFunctions[$functionName]))) { // function
+ if (isset(self::$_PHPExcelFunctions[$functionName])) {
+ $functionCall = self::$_PHPExcelFunctions[$functionName]['functionCall'];
+ $passByReference = isset(self::$_PHPExcelFunctions[$functionName]['passByReference']);
+ $passCellReference = isset(self::$_PHPExcelFunctions[$functionName]['passCellReference']);
+ } elseif (isset(self::$_controlFunctions[$functionName])) {
+ $functionCall = self::$_controlFunctions[$functionName]['functionCall'];
+ $passByReference = isset(self::$_controlFunctions[$functionName]['passByReference']);
+ $passCellReference = isset(self::$_controlFunctions[$functionName]['passCellReference']);
+ }
+ // get the arguments for this function
+// echo 'Function '.$functionName.' expects '.$argCount.' arguments
';
+ $args = $argArrayVals = array();
+ for ($i = 0; $i < $argCount; ++$i) {
+ $arg = $stack->pop();
+ $a = $argCount - $i - 1;
+ if (($passByReference) &&
+ (isset(self::$_PHPExcelFunctions[$functionName]['passByReference'][$a])) &&
+ (self::$_PHPExcelFunctions[$functionName]['passByReference'][$a])) {
+ if ($arg['reference'] === NULL) {
+ $args[] = $cellID;
+ if ($functionName != 'MKMATRIX') { $argArrayVals[] = $this->_showValue($cellID); }
+ } else {
+ $args[] = $arg['reference'];
+ if ($functionName != 'MKMATRIX') { $argArrayVals[] = $this->_showValue($arg['reference']); }
+ }
+ } else {
+ $args[] = self::_unwrapResult($arg['value']);
+ if ($functionName != 'MKMATRIX') { $argArrayVals[] = $this->_showValue($arg['value']); }
+ }
+ }
+ // Reverse the order of the arguments
+ krsort($args);
+ if (($passByReference) && ($argCount == 0)) {
+ $args[] = $cellID;
+ $argArrayVals[] = $this->_showValue($cellID);
+ }
+// echo 'Arguments are: ';
+// print_r($args);
+// echo '
';
+ if ($functionName != 'MKMATRIX') {
+ if ($this->_debugLog->getWriteDebugLog()) {
+ krsort($argArrayVals);
+ $this->_debugLog->writeDebugLog('Evaluating ', self::_localeFunc($functionName), '( ', implode(self::$_localeArgumentSeparator.' ',PHPExcel_Calculation_Functions::flattenArray($argArrayVals)), ' )');
+ }
+ }
+ // Process each argument in turn, building the return value as an array
+// if (($argCount == 1) && (is_array($args[1])) && ($functionName != 'MKMATRIX')) {
+// $operand1 = $args[1];
+// $this->_debugLog->writeDebugLog('Argument is a matrix: ', $this->_showValue($operand1));
+// $result = array();
+// $row = 0;
+// foreach($operand1 as $args) {
+// if (is_array($args)) {
+// foreach($args as $arg) {
+// $this->_debugLog->writeDebugLog('Evaluating ', self::_localeFunc($functionName), '( ', $this->_showValue($arg), ' )');
+// $r = call_user_func_array($functionCall,$arg);
+// $this->_debugLog->writeDebugLog('Evaluation Result for ', self::_localeFunc($functionName), '() function call is ', $this->_showTypeDetails($r));
+// $result[$row][] = $r;
+// }
+// ++$row;
+// } else {
+// $this->_debugLog->writeDebugLog('Evaluating ', self::_localeFunc($functionName), '( ', $this->_showValue($args), ' )');
+// $r = call_user_func_array($functionCall,$args);
+// $this->_debugLog->writeDebugLog('Evaluation Result for ', self::_localeFunc($functionName), '() function call is ', $this->_showTypeDetails($r));
+// $result[] = $r;
+// }
+// }
+// } else {
+ // Process the argument with the appropriate function call
+ if ($passCellReference) {
+ $args[] = $pCell;
+ }
+ if (strpos($functionCall,'::') !== FALSE) {
+ $result = call_user_func_array(explode('::',$functionCall),$args);
+ } else {
+ foreach($args as &$arg) {
+ $arg = PHPExcel_Calculation_Functions::flattenSingleValue($arg);
+ }
+ unset($arg);
+ $result = call_user_func_array($functionCall,$args);
+ }
+// }
+ if ($functionName != 'MKMATRIX') {
+ $this->_debugLog->writeDebugLog('Evaluation Result for ', self::_localeFunc($functionName), '() function call is ', $this->_showTypeDetails($result));
+ }
+ $stack->push('Value',self::_wrapResult($result));
+ }
+
+ } else {
+ // if the token is a number, boolean, string or an Excel error, push it onto the stack
+ if (isset(self::$_ExcelConstants[strtoupper($token)])) {
+ $excelConstant = strtoupper($token);
+// echo 'Token is a PHPExcel constant: '.$excelConstant.'
';
+ $stack->push('Constant Value',self::$_ExcelConstants[$excelConstant]);
+ $this->_debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->_showTypeDetails(self::$_ExcelConstants[$excelConstant]));
+ } elseif ((is_numeric($token)) || ($token === NULL) || (is_bool($token)) || ($token == '') || ($token{0} == '"') || ($token{0} == '#')) {
+// echo 'Token is a number, boolean, string, null or an Excel error
';
+ $stack->push('Value',$token);
+ // if the token is a named range, push the named range name onto the stack
+ } elseif (preg_match('/^'.self::CALCULATION_REGEXP_NAMEDRANGE.'$/i', $token, $matches)) {
+// echo 'Token is a named range
';
+ $namedRange = $matches[6];
+// echo 'Named Range is '.$namedRange.'
';
+ $this->_debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
+ $cellValue = $this->extractNamedRange($namedRange, ((NULL !== $pCell) ? $pCellWorksheet : NULL), FALSE);
+ $pCell->attach($pCellParent);
+ $this->_debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->_showTypeDetails($cellValue));
+ $stack->push('Named Range',$cellValue,$namedRange);
+ } else {
+ return $this->_raiseFormulaError("undefined variable '$token'");
+ }
+ }
+ }
+ // when we're out of tokens, the stack should have a single element, the final result
+ if ($stack->count() != 1) return $this->_raiseFormulaError("internal error");
+ $output = $stack->pop();
+ $output = $output['value'];
+
+// if ((is_array($output)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
+// return array_shift(PHPExcel_Calculation_Functions::flattenArray($output));
+// }
+ return $output;
+ } // function _processTokenStack()
+
+
+ private function _validateBinaryOperand($cellID, &$operand, &$stack) {
+ // Numbers, matrices and booleans can pass straight through, as they're already valid
+ if (is_string($operand)) {
+ // We only need special validations for the operand if it is a string
+ // Start by stripping off the quotation marks we use to identify true excel string values internally
+ if ($operand > '' && $operand{0} == '"') { $operand = self::_unwrapResult($operand); }
+ // If the string is a numeric value, we treat it as a numeric, so no further testing
+ if (!is_numeric($operand)) {
+ // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
+ if ($operand > '' && $operand{0} == '#') {
+ $stack->push('Value', $operand);
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($operand));
+ return FALSE;
+ } elseif (!PHPExcel_Shared_String::convertToNumberIfFraction($operand)) {
+ // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
+ $stack->push('Value', '#VALUE!');
+ $this->_debugLog->writeDebugLog('Evaluation Result is a ', $this->_showTypeDetails('#VALUE!'));
+ return FALSE;
+ }
+ }
+ }
+
+ // return a true if the value of the operand is one that we can use in normal binary operations
+ return TRUE;
+ } // function _validateBinaryOperand()
+
+
+ private function _executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, &$stack, $recursingArrays=FALSE) {
+ // If we're dealing with matrix operations, we want a matrix result
+ if ((is_array($operand1)) || (is_array($operand2))) {
+ $result = array();
+ if ((is_array($operand1)) && (!is_array($operand2))) {
+ foreach($operand1 as $x => $operandData) {
+ $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->_showValue($operandData), ' ', $operation, ' ', $this->_showValue($operand2));
+ $this->_executeBinaryComparisonOperation($cellID,$operandData,$operand2,$operation,$stack);
+ $r = $stack->pop();
+ $result[$x] = $r['value'];
+ }
+ } elseif ((!is_array($operand1)) && (is_array($operand2))) {
+ foreach($operand2 as $x => $operandData) {
+ $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->_showValue($operand1), ' ', $operation, ' ', $this->_showValue($operandData));
+ $this->_executeBinaryComparisonOperation($cellID,$operand1,$operandData,$operation,$stack);
+ $r = $stack->pop();
+ $result[$x] = $r['value'];
+ }
+ } else {
+ if (!$recursingArrays) { self::_checkMatrixOperands($operand1,$operand2,2); }
+ foreach($operand1 as $x => $operandData) {
+ $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->_showValue($operandData), ' ', $operation, ' ', $this->_showValue($operand2[$x]));
+ $this->_executeBinaryComparisonOperation($cellID,$operandData,$operand2[$x],$operation,$stack,TRUE);
+ $r = $stack->pop();
+ $result[$x] = $r['value'];
+ }
+ }
+ // Log the result details
+ $this->_debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->_showTypeDetails($result));
+ // And push the result onto the stack
+ $stack->push('Array',$result);
+ return TRUE;
+ }
+
+ // Simple validate the two operands if they are string values
+ if (is_string($operand1) && $operand1 > '' && $operand1{0} == '"') { $operand1 = self::_unwrapResult($operand1); }
+ if (is_string($operand2) && $operand2 > '' && $operand2{0} == '"') { $operand2 = self::_unwrapResult($operand2); }
+
+ // execute the necessary operation
+ switch ($operation) {
+ // Greater than
+ case '>':
+ $result = ($operand1 > $operand2);
+ break;
+ // Less than
+ case '<':
+ $result = ($operand1 < $operand2);
+ break;
+ // Equality
+ case '=':
+ $result = ($operand1 == $operand2);
+ break;
+ // Greater than or equal
+ case '>=':
+ $result = ($operand1 >= $operand2);
+ break;
+ // Less than or equal
+ case '<=':
+ $result = ($operand1 <= $operand2);
+ break;
+ // Inequality
+ case '<>':
+ $result = ($operand1 != $operand2);
+ break;
+ }
+
+ // Log the result details
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($result));
+ // And push the result onto the stack
+ $stack->push('Value',$result);
+ return TRUE;
+ } // function _executeBinaryComparisonOperation()
+
+
+ private function _executeNumericBinaryOperation($cellID,$operand1,$operand2,$operation,$matrixFunction,&$stack) {
+ // Validate the two operands
+ if (!$this->_validateBinaryOperand($cellID,$operand1,$stack)) return FALSE;
+ if (!$this->_validateBinaryOperand($cellID,$operand2,$stack)) return FALSE;
+
+ $executeMatrixOperation = FALSE;
+ // If either of the operands is a matrix, we need to treat them both as matrices
+ // (converting the other operand to a matrix if need be); then perform the required
+ // matrix operation
+ if ((is_array($operand1)) || (is_array($operand2))) {
+ // Ensure that both operands are arrays/matrices
+ $executeMatrixOperation = TRUE;
+ $mSize = array();
+ list($mSize[],$mSize[],$mSize[],$mSize[]) = self::_checkMatrixOperands($operand1,$operand2,2);
+
+ // But if they're both single cell matrices, then we can treat them as simple values
+ if (array_sum($mSize) == 4) {
+ $executeMatrixOperation = FALSE;
+ $operand1 = $operand1[0][0];
+ $operand2 = $operand2[0][0];
+ }
+ }
+
+ if ($executeMatrixOperation) {
+ try {
+ // Convert operand 1 from a PHP array to a matrix
+ $matrix = new PHPExcel_Shared_JAMA_Matrix($operand1);
+ // Perform the required operation against the operand 1 matrix, passing in operand 2
+ $matrixResult = $matrix->$matrixFunction($operand2);
+ $result = $matrixResult->getArray();
+ } catch (PHPExcel_Exception $ex) {
+ $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
+ $result = '#VALUE!';
+ }
+ } else {
+ if ((PHPExcel_Calculation_Functions::getCompatibilityMode() != PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE) &&
+ ((is_string($operand1) && !is_numeric($operand1)) || (is_string($operand2) && !is_numeric($operand2)))) {
+ $result = PHPExcel_Calculation_Functions::VALUE();
+ } else {
+ // If we're dealing with non-matrix operations, execute the necessary operation
+ switch ($operation) {
+ // Addition
+ case '+':
+ $result = $operand1+$operand2;
+ break;
+ // Subtraction
+ case '-':
+ $result = $operand1-$operand2;
+ break;
+ // Multiplication
+ case '*':
+ $result = $operand1*$operand2;
+ break;
+ // Division
+ case '/':
+ if ($operand2 == 0) {
+ // Trap for Divide by Zero error
+ $stack->push('Value','#DIV/0!');
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails('#DIV/0!'));
+ return FALSE;
+ } else {
+ $result = $operand1/$operand2;
+ }
+ break;
+ // Power
+ case '^':
+ $result = pow($operand1,$operand2);
+ break;
+ }
+ }
+ }
+
+ // Log the result details
+ $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->_showTypeDetails($result));
+ // And push the result onto the stack
+ $stack->push('Value',$result);
+ return TRUE;
+ } // function _executeNumericBinaryOperation()
+
+
+ // trigger an error, but nicely, if need be
+ protected function _raiseFormulaError($errorMessage) {
+ $this->formulaError = $errorMessage;
+ $this->_cyclicReferenceStack->clear();
+ if (!$this->suppressFormulaErrors) throw new PHPExcel_Calculation_Exception($errorMessage);
+ trigger_error($errorMessage, E_USER_ERROR);
+ } // function _raiseFormulaError()
+
+
+ /**
+ * Extract range values
+ *
+ * @param string &$pRange String based range representation
+ * @param PHPExcel_Worksheet $pSheet Worksheet
+ * @param boolean $resetLog Flag indicating whether calculation log should be reset or not
+ * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function extractCellRange(&$pRange = 'A1', PHPExcel_Worksheet $pSheet = NULL, $resetLog = TRUE) {
+ // Return value
+ $returnValue = array ();
+
+// echo 'extractCellRange('.$pRange.')',PHP_EOL;
+ if ($pSheet !== NULL) {
+ $pSheetName = $pSheet->getTitle();
+// echo 'Passed sheet name is '.$pSheetName.PHP_EOL;
+// echo 'Range reference is '.$pRange.PHP_EOL;
+ if (strpos ($pRange, '!') !== false) {
+// echo '$pRange reference includes sheet reference',PHP_EOL;
+ list($pSheetName,$pRange) = PHPExcel_Worksheet::extractSheetTitle($pRange, true);
+// echo 'New sheet name is '.$pSheetName,PHP_EOL;
+// echo 'Adjusted Range reference is '.$pRange,PHP_EOL;
+ $pSheet = $this->_workbook->getSheetByName($pSheetName);
+ }
+
+ // Extract range
+ $aReferences = PHPExcel_Cell::extractAllCellReferencesInRange($pRange);
+ $pRange = $pSheetName.'!'.$pRange;
+ if (!isset($aReferences[1])) {
+ // Single cell in range
+ sscanf($aReferences[0],'%[A-Z]%d', $currentCol, $currentRow);
+ $cellValue = NULL;
+ if ($pSheet->cellExists($aReferences[0])) {
+ $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
+ } else {
+ $returnValue[$currentRow][$currentCol] = NULL;
+ }
+ } else {
+ // Extract cell data for all cells in the range
+ foreach ($aReferences as $reference) {
+ // Extract range
+ sscanf($reference,'%[A-Z]%d', $currentCol, $currentRow);
+ $cellValue = NULL;
+ if ($pSheet->cellExists($reference)) {
+ $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
+ } else {
+ $returnValue[$currentRow][$currentCol] = NULL;
+ }
+ }
+ }
+ }
+
+ // Return
+ return $returnValue;
+ } // function extractCellRange()
+
+
+ /**
+ * Extract range values
+ *
+ * @param string &$pRange String based range representation
+ * @param PHPExcel_Worksheet $pSheet Worksheet
+ * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
+ * @param boolean $resetLog Flag indicating whether calculation log should be reset or not
+ * @throws PHPExcel_Calculation_Exception
+ */
+ public function extractNamedRange(&$pRange = 'A1', PHPExcel_Worksheet $pSheet = NULL, $resetLog = TRUE) {
+ // Return value
+ $returnValue = array ();
+
+// echo 'extractNamedRange('.$pRange.')
';
+ if ($pSheet !== NULL) {
+ $pSheetName = $pSheet->getTitle();
+// echo 'Current sheet name is '.$pSheetName.'
';
+// echo 'Range reference is '.$pRange.'
';
+ if (strpos ($pRange, '!') !== false) {
+// echo '$pRange reference includes sheet reference',PHP_EOL;
+ list($pSheetName,$pRange) = PHPExcel_Worksheet::extractSheetTitle($pRange, true);
+// echo 'New sheet name is '.$pSheetName,PHP_EOL;
+// echo 'Adjusted Range reference is '.$pRange,PHP_EOL;
+ $pSheet = $this->_workbook->getSheetByName($pSheetName);
+ }
+
+ // Named range?
+ $namedRange = PHPExcel_NamedRange::resolveRange($pRange, $pSheet);
+ if ($namedRange !== NULL) {
+ $pSheet = $namedRange->getWorksheet();
+// echo 'Named Range '.$pRange.' (';
+ $pRange = $namedRange->getRange();
+ $splitRange = PHPExcel_Cell::splitRange($pRange);
+ // Convert row and column references
+ if (ctype_alpha($splitRange[0][0])) {
+ $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
+ } elseif(ctype_digit($splitRange[0][0])) {
+ $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
+ }
+// echo $pRange.') is in sheet '.$namedRange->getWorksheet()->getTitle().'
';
+
+// if ($pSheet->getTitle() != $namedRange->getWorksheet()->getTitle()) {
+// if (!$namedRange->getLocalOnly()) {
+// $pSheet = $namedRange->getWorksheet();
+// } else {
+// return $returnValue;
+// }
+// }
+ } else {
+ return PHPExcel_Calculation_Functions::REF();
+ }
+
+ // Extract range
+ $aReferences = PHPExcel_Cell::extractAllCellReferencesInRange($pRange);
+// var_dump($aReferences);
+ if (!isset($aReferences[1])) {
+ // Single cell (or single column or row) in range
+ list($currentCol,$currentRow) = PHPExcel_Cell::coordinateFromString($aReferences[0]);
+ $cellValue = NULL;
+ if ($pSheet->cellExists($aReferences[0])) {
+ $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
+ } else {
+ $returnValue[$currentRow][$currentCol] = NULL;
+ }
+ } else {
+ // Extract cell data for all cells in the range
+ foreach ($aReferences as $reference) {
+ // Extract range
+ list($currentCol,$currentRow) = PHPExcel_Cell::coordinateFromString($reference);
+// echo 'NAMED RANGE: $currentCol='.$currentCol.' $currentRow='.$currentRow.'
';
+ $cellValue = NULL;
+ if ($pSheet->cellExists($reference)) {
+ $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
+ } else {
+ $returnValue[$currentRow][$currentCol] = NULL;
+ }
+ }
+ }
+// print_r($returnValue);
+// echo '
';
+ }
+
+ // Return
+ return $returnValue;
+ } // function extractNamedRange()
+
+
+ /**
+ * Is a specific function implemented?
+ *
+ * @param string $pFunction Function Name
+ * @return boolean
+ */
+ public function isImplemented($pFunction = '') {
+ $pFunction = strtoupper ($pFunction);
+ if (isset(self::$_PHPExcelFunctions[$pFunction])) {
+ return (self::$_PHPExcelFunctions[$pFunction]['functionCall'] != 'PHPExcel_Calculation_Functions::DUMMY');
+ } else {
+ return FALSE;
+ }
+ } // function isImplemented()
+
+
+ /**
+ * Get a list of all implemented functions as an array of function objects
+ *
+ * @return array of PHPExcel_Calculation_Function
+ */
+ public function listFunctions() {
+ // Return value
+ $returnValue = array();
+ // Loop functions
+ foreach(self::$_PHPExcelFunctions as $functionName => $function) {
+ if ($function['functionCall'] != 'PHPExcel_Calculation_Functions::DUMMY') {
+ $returnValue[$functionName] = new PHPExcel_Calculation_Function($function['category'],
+ $functionName,
+ $function['functionCall']
+ );
+ }
+ }
+
+ // Return
+ return $returnValue;
+ } // function listFunctions()
+
+
+ /**
+ * Get a list of all Excel function names
+ *
+ * @return array
+ */
+ public function listAllFunctionNames() {
+ return array_keys(self::$_PHPExcelFunctions);
+ } // function listAllFunctionNames()
+
+ /**
+ * Get a list of implemented Excel function names
+ *
+ * @return array
+ */
+ public function listFunctionNames() {
+ // Return value
+ $returnValue = array();
+ // Loop functions
+ foreach(self::$_PHPExcelFunctions as $functionName => $function) {
+ if ($function['functionCall'] != 'PHPExcel_Calculation_Functions::DUMMY') {
+ $returnValue[] = $functionName;
+ }
+ }
+
+ // Return
+ return $returnValue;
+ } // function listFunctionNames()
+
+} // class PHPExcel_Calculation
+
diff --git a/framework/library/phpexcel/PHPExcel/Cell.php b/framework/library/phpexcel/PHPExcel/Cell.php
new file mode 100644
index 0000000..e652a55
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Cell.php
@@ -0,0 +1,978 @@
+_parent->updateCacheData($this);
+
+ return $this;
+ }
+
+ public function detach() {
+ $this->_parent = NULL;
+ }
+
+ public function attach(PHPExcel_CachedObjectStorage_CacheBase $parent) {
+
+
+ $this->_parent = $parent;
+ }
+
+
+ /**
+ * Create a new Cell
+ *
+ * @param mixed $pValue
+ * @param string $pDataType
+ * @param PHPExcel_Worksheet $pSheet
+ * @throws PHPExcel_Exception
+ */
+ public function __construct($pValue = NULL, $pDataType = NULL, PHPExcel_Worksheet $pSheet = NULL)
+ {
+ // Initialise cell value
+ $this->_value = $pValue;
+
+ // Set worksheet cache
+ $this->_parent = $pSheet->getCellCacheController();
+
+ // Set datatype?
+ if ($pDataType !== NULL) {
+ if ($pDataType == PHPExcel_Cell_DataType::TYPE_STRING2)
+ $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
+ $this->_dataType = $pDataType;
+ } else {
+ if (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new PHPExcel_Exception("Value could not be bound to cell.");
+ }
+ }
+
+ // set default index to cellXf
+ $this->_xfIndex = 0;
+ }
+
+ /**
+ * Get cell coordinate column
+ *
+ * @return string
+ */
+ public function getColumn()
+ {
+ return $this->_parent->getCurrentColumn();
+ }
+
+ /**
+ * Get cell coordinate row
+ *
+ * @return int
+ */
+ public function getRow()
+ {
+ return $this->_parent->getCurrentRow();
+ }
+
+ /**
+ * Get cell coordinate
+ *
+ * @return string
+ */
+ public function getCoordinate()
+ {
+ return $this->_parent->getCurrentAddress();
+ }
+
+ /**
+ * Get cell value
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Get cell value with formatting
+ *
+ * @return string
+ */
+ public function getFormattedValue()
+ {
+ return (string) PHPExcel_Style_NumberFormat::toFormattedString(
+ $this->getCalculatedValue(),
+ $this->getWorksheet()->getParent()->getCellXfByIndex($this->getXfIndex())
+ ->getNumberFormat()->getFormatCode()
+ );
+ }
+
+ /**
+ * Set cell value
+ *
+ * Sets the value for a cell, automatically determining the datatype using the value binder
+ *
+ * @param mixed $pValue Value
+ * @return PHPExcel_Cell
+ * @throws PHPExcel_Exception
+ */
+ public function setValue($pValue = NULL)
+ {
+ if (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new PHPExcel_Exception("Value could not be bound to cell.");
+ }
+ return $this;
+ }
+
+ /**
+ * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder)
+ *
+ * @param mixed $pValue Value
+ * @param string $pDataType Explicit data type
+ * @return PHPExcel_Cell
+ * @throws PHPExcel_Exception
+ */
+ public function setValueExplicit($pValue = NULL, $pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
+ {
+ // set the value according to data type
+ switch ($pDataType) {
+ case PHPExcel_Cell_DataType::TYPE_STRING2:
+ $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
+ case PHPExcel_Cell_DataType::TYPE_STRING:
+ case PHPExcel_Cell_DataType::TYPE_NULL:
+ case PHPExcel_Cell_DataType::TYPE_INLINE:
+ $this->_value = PHPExcel_Cell_DataType::checkString($pValue);
+ break;
+ case PHPExcel_Cell_DataType::TYPE_NUMERIC:
+ $this->_value = (float)$pValue;
+ break;
+ case PHPExcel_Cell_DataType::TYPE_FORMULA:
+ $this->_value = (string)$pValue;
+ break;
+ case PHPExcel_Cell_DataType::TYPE_BOOL:
+ $this->_value = (bool)$pValue;
+ break;
+ case PHPExcel_Cell_DataType::TYPE_ERROR:
+ $this->_value = PHPExcel_Cell_DataType::checkErrorCode($pValue);
+ break;
+ default:
+ throw new PHPExcel_Exception('Invalid datatype: ' . $pDataType);
+ break;
+ }
+
+ // set the datatype
+ $this->_dataType = $pDataType;
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Get calculated cell value
+ *
+ * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
+ *
+ * @param boolean $resetLog Whether the calculation engine logger should be reset or not
+ * @return mixed
+ * @throws PHPExcel_Exception
+ */
+ public function getCalculatedValue($resetLog = TRUE)
+ {
+//echo 'Cell '.$this->getCoordinate().' value is a '.$this->_dataType.' with a value of '.$this->getValue().PHP_EOL;
+ if ($this->_dataType == PHPExcel_Cell_DataType::TYPE_FORMULA) {
+ try {
+//echo 'Cell value for '.$this->getCoordinate().' is a formula: Calculating value'.PHP_EOL;
+ $result = PHPExcel_Calculation::getInstance(
+ $this->getWorksheet()->getParent()
+ )->calculateCellValue($this,$resetLog);
+//echo $this->getCoordinate().' calculation result is '.$result.PHP_EOL;
+ // We don't yet handle array returns
+ if (is_array($result)) {
+ while (is_array($result)) {
+ $result = array_pop($result);
+ }
+ }
+ } catch ( PHPExcel_Exception $ex ) {
+ if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->_calculatedValue !== NULL)) {
+//echo 'Returning fallback value of '.$this->_calculatedValue.' for cell '.$this->getCoordinate().PHP_EOL;
+ return $this->_calculatedValue; // Fallback for calculations referencing external files.
+ }
+//echo 'Calculation Exception: '.$ex->getMessage().PHP_EOL;
+ $result = '#N/A';
+ throw new PHPExcel_Calculation_Exception(
+ $this->getWorksheet()->getTitle().'!'.$this->getCoordinate().' -> '.$ex->getMessage()
+ );
+ }
+
+ if ($result === '#Not Yet Implemented') {
+//echo 'Returning fallback value of '.$this->_calculatedValue.' for cell '.$this->getCoordinate().PHP_EOL;
+ return $this->_calculatedValue; // Fallback if calculation engine does not support the formula.
+ }
+//echo 'Returning calculated value of '.$result.' for cell '.$this->getCoordinate().PHP_EOL;
+ return $result;
+ } elseif($this->_value instanceof PHPExcel_RichText) {
+// echo 'Cell value for '.$this->getCoordinate().' is rich text: Returning data value of '.$this->_value.'
';
+ return $this->_value->getPlainText();
+ }
+// echo 'Cell value for '.$this->getCoordinate().' is not a formula: Returning data value of '.$this->_value.'
';
+ return $this->_value;
+ }
+
+ /**
+ * Set old calculated value (cached)
+ *
+ * @param mixed $pValue Value
+ * @return PHPExcel_Cell
+ */
+ public function setCalculatedValue($pValue = NULL)
+ {
+ if ($pValue !== NULL) {
+ $this->_calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
+ }
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Get old calculated value (cached)
+ * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
+ * create the original spreadsheet file.
+ * Note that this value is not guaranteed to refelect the actual calculated value because it is
+ * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
+ * values used by the formula have changed since it was last calculated.
+ *
+ * @return mixed
+ */
+ public function getOldCalculatedValue()
+ {
+ return $this->_calculatedValue;
+ }
+
+ /**
+ * Get cell data type
+ *
+ * @return string
+ */
+ public function getDataType()
+ {
+ return $this->_dataType;
+ }
+
+ /**
+ * Set cell data type
+ *
+ * @param string $pDataType
+ * @return PHPExcel_Cell
+ */
+ public function setDataType($pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
+ {
+ if ($pDataType == PHPExcel_Cell_DataType::TYPE_STRING2)
+ $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
+
+ $this->_dataType = $pDataType;
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Does this cell contain Data validation rules?
+ *
+ * @return boolean
+ * @throws PHPExcel_Exception
+ */
+ public function hasDataValidation()
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot check for data validation when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Data validation rules
+ *
+ * @return PHPExcel_Cell_DataValidation
+ * @throws PHPExcel_Exception
+ */
+ public function getDataValidation()
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot get data validation for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getDataValidation($this->getCoordinate());
+ }
+
+ /**
+ * Set Data validation rules
+ *
+ * @param PHPExcel_Cell_DataValidation $pDataValidation
+ * @return PHPExcel_Cell
+ * @throws PHPExcel_Exception
+ */
+ public function setDataValidation(PHPExcel_Cell_DataValidation $pDataValidation = NULL)
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot set data validation for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Does this cell contain a Hyperlink?
+ *
+ * @return boolean
+ * @throws PHPExcel_Exception
+ */
+ public function hasHyperlink()
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Hyperlink
+ *
+ * @return PHPExcel_Cell_Hyperlink
+ * @throws PHPExcel_Exception
+ */
+ public function getHyperlink()
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getHyperlink($this->getCoordinate());
+ }
+
+ /**
+ * Set Hyperlink
+ *
+ * @param PHPExcel_Cell_Hyperlink $pHyperlink
+ * @return PHPExcel_Cell
+ * @throws PHPExcel_Exception
+ */
+ public function setHyperlink(PHPExcel_Cell_Hyperlink $pHyperlink = NULL)
+ {
+ if (!isset($this->_parent)) {
+ throw new PHPExcel_Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Get parent worksheet
+ *
+ * @return PHPExcel_Worksheet
+ */
+ public function getParent() {
+ return $this->_parent;
+ }
+
+ /**
+ * Get parent worksheet
+ *
+ * @return PHPExcel_Worksheet
+ */
+ public function getWorksheet() {
+ return $this->_parent->getParent();
+ }
+
+ /**
+ * Get cell style
+ *
+ * @return PHPExcel_Style
+ */
+ public function getStyle()
+ {
+ return $this->getWorksheet()->getParent()->getCellXfByIndex($this->getXfIndex());
+ }
+
+ /**
+ * Re-bind parent
+ *
+ * @param PHPExcel_Worksheet $parent
+ * @return PHPExcel_Cell
+ */
+ public function rebindParent(PHPExcel_Worksheet $parent) {
+ $this->_parent = $parent->getCellCacheController();
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * Is cell in a specific range?
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ * @return boolean
+ */
+ public function isInRange($pRange = 'A1:A1')
+ {
+ list($rangeStart,$rangeEnd) = self::rangeBoundaries($pRange);
+
+ // Translate properties
+ $myColumn = self::columnIndexFromString($this->getColumn());
+ $myRow = $this->getRow();
+
+ // Verify if cell is in range
+ return (($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
+ ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow)
+ );
+ }
+
+ /**
+ * Coordinate from string
+ *
+ * @param string $pCoordinateString
+ * @return array Array containing column and row (indexes 0 and 1)
+ * @throws PHPExcel_Exception
+ */
+ public static function coordinateFromString($pCoordinateString = 'A1')
+ {
+ if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) {
+ return array($matches[1],$matches[2]);
+ } elseif ((strpos($pCoordinateString,':') !== FALSE) || (strpos($pCoordinateString,',') !== FALSE)) {
+ throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
+ } elseif ($pCoordinateString == '') {
+ throw new PHPExcel_Exception('Cell coordinate can not be zero-length string');
+ }
+
+ throw new PHPExcel_Exception('Invalid cell coordinate '.$pCoordinateString);
+ }
+
+ /**
+ * Make string row, column or cell coordinate absolute
+ *
+ * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
+ * Note that this value can be a row or column reference as well as a cell reference
+ * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
+ * @throws PHPExcel_Exception
+ */
+ public static function absoluteReference($pCoordinateString = 'A1')
+ {
+ if (strpos($pCoordinateString,':') === FALSE && strpos($pCoordinateString,',') === FALSE) {
+ // Split out any worksheet name from the reference
+ $worksheet = '';
+ $cellAddress = explode('!',$pCoordinateString);
+ if (count($cellAddress) > 1) {
+ list($worksheet,$pCoordinateString) = $cellAddress;
+ }
+ if ($worksheet > '') $worksheet .= '!';
+
+ // Create absolute coordinate
+ if (ctype_digit($pCoordinateString)) {
+ return $worksheet . '$' . $pCoordinateString;
+ } elseif (ctype_alpha($pCoordinateString)) {
+ return $worksheet . '$' . strtoupper($pCoordinateString);
+ }
+ return $worksheet . self::absoluteCoordinate($pCoordinateString);
+ }
+
+ throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ /**
+ * Make string coordinate absolute
+ *
+ * @param string $pCoordinateString e.g. 'A1'
+ * @return string Absolute coordinate e.g. '$A$1'
+ * @throws PHPExcel_Exception
+ */
+ public static function absoluteCoordinate($pCoordinateString = 'A1')
+ {
+ if (strpos($pCoordinateString,':') === FALSE && strpos($pCoordinateString,',') === FALSE) {
+ // Split out any worksheet name from the coordinate
+ $worksheet = '';
+ $cellAddress = explode('!',$pCoordinateString);
+ if (count($cellAddress) > 1) {
+ list($worksheet,$pCoordinateString) = $cellAddress;
+ }
+ if ($worksheet > '') $worksheet .= '!';
+
+ // Create absolute coordinate
+ list($column, $row) = self::coordinateFromString($pCoordinateString);
+ $column = ltrim($column,'$');
+ $row = ltrim($row,'$');
+ return $worksheet . '$' . $column . '$' . $row;
+ }
+
+ throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ /**
+ * Split range into coordinate strings
+ *
+ * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
+ * @return array Array containg one or more arrays containing one or two coordinate strings
+ * e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11'))
+ * or array('B4')
+ */
+ public static function splitRange($pRange = 'A1:A1')
+ {
+ // Ensure $pRange is a valid range
+ if(empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ $exploded = explode(',', $pRange);
+ $counter = count($exploded);
+ for ($i = 0; $i < $counter; ++$i) {
+ $exploded[$i] = explode(':', $exploded[$i]);
+ }
+ return $exploded;
+ }
+
+ /**
+ * Build range from coordinate strings
+ *
+ * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
+ * @return string String representation of $pRange
+ * @throws PHPExcel_Exception
+ */
+ public static function buildRange($pRange)
+ {
+ // Verify range
+ if (!is_array($pRange) || empty($pRange) || !is_array($pRange[0])) {
+ throw new PHPExcel_Exception('Range does not contain any information');
+ }
+
+ // Build range
+ $imploded = array();
+ $counter = count($pRange);
+ for ($i = 0; $i < $counter; ++$i) {
+ $pRange[$i] = implode(':', $pRange[$i]);
+ }
+ $imploded = implode(',', $pRange);
+
+ return $imploded;
+ }
+
+ /**
+ * Calculate range boundaries
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ * @return array Range coordinates array(Start Cell, End Cell)
+ * where Start Cell and End Cell are arrays (Column Number, Row Number)
+ */
+ public static function rangeBoundaries($pRange = 'A1:A1')
+ {
+ // Ensure $pRange is a valid range
+ if(empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === FALSE) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ list($rangeA, $rangeB) = explode(':', $pRange);
+ }
+
+ // Calculate range outer borders
+ $rangeStart = self::coordinateFromString($rangeA);
+ $rangeEnd = self::coordinateFromString($rangeB);
+
+ // Translate column into index
+ $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
+ $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
+
+ return array($rangeStart, $rangeEnd);
+ }
+
+ /**
+ * Calculate range dimension
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ * @return array Range dimension (width, height)
+ */
+ public static function rangeDimension($pRange = 'A1:A1')
+ {
+ // Calculate range outer borders
+ list($rangeStart,$rangeEnd) = self::rangeBoundaries($pRange);
+
+ return array( ($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1) );
+ }
+
+ /**
+ * Calculate range boundaries
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ * @return array Range coordinates array(Start Cell, End Cell)
+ * where Start Cell and End Cell are arrays (Column ID, Row Number)
+ */
+ public static function getRangeBoundaries($pRange = 'A1:A1')
+ {
+ // Ensure $pRange is a valid range
+ if(empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === FALSE) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ list($rangeA, $rangeB) = explode(':', $pRange);
+ }
+
+ return array( self::coordinateFromString($rangeA), self::coordinateFromString($rangeB));
+ }
+
+ /**
+ * Column index from string
+ *
+ * @param string $pString
+ * @return int Column index (base 1 !!!)
+ */
+ public static function columnIndexFromString($pString = 'A')
+ {
+ // Using a lookup cache adds a slight memory overhead, but boosts speed
+ // caching using a static within the method is faster than a class static,
+ // though it's additional memory overhead
+ static $_indexCache = array();
+
+ if (isset($_indexCache[$pString]))
+ return $_indexCache[$pString];
+
+ // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
+ // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
+ // memory overhead either
+ static $_columnLookup = array(
+ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
+ 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
+ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
+ 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26
+ );
+
+ // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
+ // for improved performance
+ if (isset($pString{0})) {
+ if (!isset($pString{1})) {
+ $_indexCache[$pString] = $_columnLookup[$pString];
+ return $_indexCache[$pString];
+ } elseif(!isset($pString{2})) {
+ $_indexCache[$pString] = $_columnLookup[$pString{0}] * 26 + $_columnLookup[$pString{1}];
+ return $_indexCache[$pString];
+ } elseif(!isset($pString{3})) {
+ $_indexCache[$pString] = $_columnLookup[$pString{0}] * 676 + $_columnLookup[$pString{1}] * 26 + $_columnLookup[$pString{2}];
+ return $_indexCache[$pString];
+ }
+ }
+ throw new PHPExcel_Exception("Column string index can not be " . ((isset($pString{0})) ? "longer than 3 characters" : "empty"));
+ }
+
+ /**
+ * String from columnindex
+ *
+ * @param int $pColumnIndex Column index (base 0 !!!)
+ * @return string
+ */
+ public static function stringFromColumnIndex($pColumnIndex = 0)
+ {
+ // Using a lookup cache adds a slight memory overhead, but boosts speed
+ // caching using a static within the method is faster than a class static,
+ // though it's additional memory overhead
+ static $_indexCache = array();
+
+ if (!isset($_indexCache[$pColumnIndex])) {
+ // Determine column string
+ if ($pColumnIndex < 26) {
+ $_indexCache[$pColumnIndex] = chr(65 + $pColumnIndex);
+ } elseif ($pColumnIndex < 702) {
+ $_indexCache[$pColumnIndex] = chr(64 + ($pColumnIndex / 26)) .
+ chr(65 + $pColumnIndex % 26);
+ } else {
+ $_indexCache[$pColumnIndex] = chr(64 + (($pColumnIndex - 26) / 676)) .
+ chr(65 + ((($pColumnIndex - 26) % 676) / 26)) .
+ chr(65 + $pColumnIndex % 26);
+ }
+ }
+ return $_indexCache[$pColumnIndex];
+ }
+
+ /**
+ * Extract all cell references in range
+ *
+ * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
+ * @return array Array containing single cell references
+ */
+ public static function extractAllCellReferencesInRange($pRange = 'A1') {
+ // Returnvalue
+ $returnValue = array();
+
+ // Explode spaces
+ $cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
+ foreach ($cellBlocks as $cellBlock) {
+ // Single cell?
+ if (strpos($cellBlock,':') === FALSE && strpos($cellBlock,',') === FALSE) {
+ $returnValue[] = $cellBlock;
+ continue;
+ }
+
+ // Range...
+ $ranges = self::splitRange($cellBlock);
+ foreach($ranges as $range) {
+ // Single cell?
+ if (!isset($range[1])) {
+ $returnValue[] = $range[0];
+ continue;
+ }
+
+ // Range...
+ list($rangeStart, $rangeEnd) = $range;
+ sscanf($rangeStart,'%[A-Z]%d', $startCol, $startRow);
+ sscanf($rangeEnd,'%[A-Z]%d', $endCol, $endRow);
+ $endCol++;
+
+ // Current data
+ $currentCol = $startCol;
+ $currentRow = $startRow;
+
+ // Loop cells
+ while ($currentCol != $endCol) {
+ while ($currentRow <= $endRow) {
+ $returnValue[] = $currentCol.$currentRow;
+ ++$currentRow;
+ }
+ ++$currentCol;
+ $currentRow = $startRow;
+ }
+ }
+ }
+
+ // Sort the result by column and row
+ $sortKeys = array();
+ foreach (array_unique($returnValue) as $coord) {
+ sscanf($coord,'%[A-Z]%d', $column, $row);
+ $sortKeys[sprintf('%3s%09d',$column,$row)] = $coord;
+ }
+ ksort($sortKeys);
+
+ // Return value
+ return array_values($sortKeys);
+ }
+
+ /**
+ * Compare 2 cells
+ *
+ * @param PHPExcel_Cell $a Cell a
+ * @param PHPExcel_Cell $b Cell b
+ * @return int Result of comparison (always -1 or 1, never zero!)
+ */
+ public static function compareCells(PHPExcel_Cell $a, PHPExcel_Cell $b)
+ {
+ if ($a->getRow() < $b->getRow()) {
+ return -1;
+ } elseif ($a->getRow() > $b->getRow()) {
+ return 1;
+ } elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Get value binder to use
+ *
+ * @return PHPExcel_Cell_IValueBinder
+ */
+ public static function getValueBinder() {
+ if (self::$_valueBinder === NULL) {
+ self::$_valueBinder = new PHPExcel_Cell_DefaultValueBinder();
+ }
+
+ return self::$_valueBinder;
+ }
+
+ /**
+ * Set value binder to use
+ *
+ * @param PHPExcel_Cell_IValueBinder $binder
+ * @throws PHPExcel_Exception
+ */
+ public static function setValueBinder(PHPExcel_Cell_IValueBinder $binder = NULL) {
+ if ($binder === NULL) {
+ throw new PHPExcel_Exception("A PHPExcel_Cell_IValueBinder is required for PHPExcel to function correctly.");
+ }
+
+ self::$_valueBinder = $binder;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone() {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if ((is_object($value)) && ($key != '_parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Get index to cellXf
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->_xfIndex;
+ }
+
+ /**
+ * Set index to cellXf
+ *
+ * @param int $pValue
+ * @return PHPExcel_Cell
+ */
+ public function setXfIndex($pValue = 0)
+ {
+ $this->_xfIndex = $pValue;
+
+ return $this->notifyCacheController();
+ }
+
+ /**
+ * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
+ */
+ public function setFormulaAttributes($pAttributes)
+ {
+ $this->_formulaAttributes = $pAttributes;
+ return $this;
+ }
+
+ /**
+ * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
+ */
+ public function getFormulaAttributes()
+ {
+ return $this->_formulaAttributes;
+ }
+
+ /**
+ * Convert to string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->getValue();
+ }
+
+}
+
diff --git a/framework/library/phpexcel/PHPExcel/Chart.php b/framework/library/phpexcel/PHPExcel/Chart.php
new file mode 100644
index 0000000..25249a3
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Chart.php
@@ -0,0 +1,551 @@
+_name = $name;
+ $this->_title = $title;
+ $this->_legend = $legend;
+ $this->_xAxisLabel = $xAxisLabel;
+ $this->_yAxisLabel = $yAxisLabel;
+ $this->_plotArea = $plotArea;
+ $this->_plotVisibleOnly = $plotVisibleOnly;
+ $this->_displayBlanksAs = $displayBlanksAs;
+ }
+
+ /**
+ * Get Name
+ *
+ * @return string
+ */
+ public function getName() {
+ return $this->_name;
+ }
+
+ /**
+ * Get Worksheet
+ *
+ * @return PHPExcel_Worksheet
+ */
+ public function getWorksheet() {
+ return $this->_worksheet;
+ }
+
+ /**
+ * Set Worksheet
+ *
+ * @param PHPExcel_Worksheet $pValue
+ * @throws PHPExcel_Chart_Exception
+ * @return PHPExcel_Chart
+ */
+ public function setWorksheet(PHPExcel_Worksheet $pValue = null) {
+ $this->_worksheet = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Title
+ *
+ * @return PHPExcel_Chart_Title
+ */
+ public function getTitle() {
+ return $this->_title;
+ }
+
+ /**
+ * Set Title
+ *
+ * @param PHPExcel_Chart_Title $title
+ * @return PHPExcel_Chart
+ */
+ public function setTitle(PHPExcel_Chart_Title $title) {
+ $this->_title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get Legend
+ *
+ * @return PHPExcel_Chart_Legend
+ */
+ public function getLegend() {
+ return $this->_legend;
+ }
+
+ /**
+ * Set Legend
+ *
+ * @param PHPExcel_Chart_Legend $legend
+ * @return PHPExcel_Chart
+ */
+ public function setLegend(PHPExcel_Chart_Legend $legend) {
+ $this->_legend = $legend;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Axis Label
+ *
+ * @return PHPExcel_Chart_Title
+ */
+ public function getXAxisLabel() {
+ return $this->_xAxisLabel;
+ }
+
+ /**
+ * Set X-Axis Label
+ *
+ * @param PHPExcel_Chart_Title $label
+ * @return PHPExcel_Chart
+ */
+ public function setXAxisLabel(PHPExcel_Chart_Title $label) {
+ $this->_xAxisLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Axis Label
+ *
+ * @return PHPExcel_Chart_Title
+ */
+ public function getYAxisLabel() {
+ return $this->_yAxisLabel;
+ }
+
+ /**
+ * Set Y-Axis Label
+ *
+ * @param PHPExcel_Chart_Title $label
+ * @return PHPExcel_Chart
+ */
+ public function setYAxisLabel(PHPExcel_Chart_Title $label) {
+ $this->_yAxisLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Area
+ *
+ * @return PHPExcel_Chart_PlotArea
+ */
+ public function getPlotArea() {
+ return $this->_plotArea;
+ }
+
+ /**
+ * Get Plot Visible Only
+ *
+ * @return boolean
+ */
+ public function getPlotVisibleOnly() {
+ return $this->_plotVisibleOnly;
+ }
+
+ /**
+ * Set Plot Visible Only
+ *
+ * @param boolean $plotVisibleOnly
+ * @return PHPExcel_Chart
+ */
+ public function setPlotVisibleOnly($plotVisibleOnly = true) {
+ $this->_plotVisibleOnly = $plotVisibleOnly;
+
+ return $this;
+ }
+
+ /**
+ * Get Display Blanks as
+ *
+ * @return string
+ */
+ public function getDisplayBlanksAs() {
+ return $this->_displayBlanksAs;
+ }
+
+ /**
+ * Set Display Blanks as
+ *
+ * @param string $displayBlanksAs
+ * @return PHPExcel_Chart
+ */
+ public function setDisplayBlanksAs($displayBlanksAs = '0') {
+ $this->_displayBlanksAs = $displayBlanksAs;
+ }
+
+
+ /**
+ * Set the Top Left position for the chart
+ *
+ * @param string $cell
+ * @param integer $xOffset
+ * @param integer $yOffset
+ * @return PHPExcel_Chart
+ */
+ public function setTopLeftPosition($cell, $xOffset=null, $yOffset=null) {
+ $this->_topLeftCellRef = $cell;
+ if (!is_null($xOffset))
+ $this->setTopLeftXOffset($xOffset);
+ if (!is_null($yOffset))
+ $this->setTopLeftYOffset($yOffset);
+
+ return $this;
+ }
+
+ /**
+ * Get the top left position of the chart
+ *
+ * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
+ */
+ public function getTopLeftPosition() {
+ return array( 'cell' => $this->_topLeftCellRef,
+ 'xOffset' => $this->_topLeftXOffset,
+ 'yOffset' => $this->_topLeftYOffset
+ );
+ }
+
+ /**
+ * Get the cell address where the top left of the chart is fixed
+ *
+ * @return string
+ */
+ public function getTopLeftCell() {
+ return $this->_topLeftCellRef;
+ }
+
+ /**
+ * Set the Top Left cell position for the chart
+ *
+ * @param string $cell
+ * @return PHPExcel_Chart
+ */
+ public function setTopLeftCell($cell) {
+ $this->_topLeftCellRef = $cell;
+
+ return $this;
+ }
+
+ /**
+ * Set the offset position within the Top Left cell for the chart
+ *
+ * @param integer $xOffset
+ * @param integer $yOffset
+ * @return PHPExcel_Chart
+ */
+ public function setTopLeftOffset($xOffset=null,$yOffset=null) {
+ if (!is_null($xOffset))
+ $this->setTopLeftXOffset($xOffset);
+ if (!is_null($yOffset))
+ $this->setTopLeftYOffset($yOffset);
+
+ return $this;
+ }
+
+ /**
+ * Get the offset position within the Top Left cell for the chart
+ *
+ * @return integer[]
+ */
+ public function getTopLeftOffset() {
+ return array( 'X' => $this->_topLeftXOffset,
+ 'Y' => $this->_topLeftYOffset
+ );
+ }
+
+ public function setTopLeftXOffset($xOffset) {
+ $this->_topLeftXOffset = $xOffset;
+
+ return $this;
+ }
+
+ public function getTopLeftXOffset() {
+ return $this->_topLeftXOffset;
+ }
+
+ public function setTopLeftYOffset($yOffset) {
+ $this->_topLeftYOffset = $yOffset;
+
+ return $this;
+ }
+
+ public function getTopLeftYOffset() {
+ return $this->_topLeftYOffset;
+ }
+
+ /**
+ * Set the Bottom Right position of the chart
+ *
+ * @param string $cell
+ * @param integer $xOffset
+ * @param integer $yOffset
+ * @return PHPExcel_Chart
+ */
+ public function setBottomRightPosition($cell, $xOffset=null, $yOffset=null) {
+ $this->_bottomRightCellRef = $cell;
+ if (!is_null($xOffset))
+ $this->setBottomRightXOffset($xOffset);
+ if (!is_null($yOffset))
+ $this->setBottomRightYOffset($yOffset);
+
+ return $this;
+ }
+
+ /**
+ * Get the bottom right position of the chart
+ *
+ * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
+ */
+ public function getBottomRightPosition() {
+ return array( 'cell' => $this->_bottomRightCellRef,
+ 'xOffset' => $this->_bottomRightXOffset,
+ 'yOffset' => $this->_bottomRightYOffset
+ );
+ }
+
+ public function setBottomRightCell($cell) {
+ $this->_bottomRightCellRef = $cell;
+
+ return $this;
+ }
+
+ /**
+ * Get the cell address where the bottom right of the chart is fixed
+ *
+ * @return string
+ */
+ public function getBottomRightCell() {
+ return $this->_bottomRightCellRef;
+ }
+
+ /**
+ * Set the offset position within the Bottom Right cell for the chart
+ *
+ * @param integer $xOffset
+ * @param integer $yOffset
+ * @return PHPExcel_Chart
+ */
+ public function setBottomRightOffset($xOffset=null,$yOffset=null) {
+ if (!is_null($xOffset))
+ $this->setBottomRightXOffset($xOffset);
+ if (!is_null($yOffset))
+ $this->setBottomRightYOffset($yOffset);
+
+ return $this;
+ }
+
+ /**
+ * Get the offset position within the Bottom Right cell for the chart
+ *
+ * @return integer[]
+ */
+ public function getBottomRightOffset() {
+ return array( 'X' => $this->_bottomRightXOffset,
+ 'Y' => $this->_bottomRightYOffset
+ );
+ }
+
+ public function setBottomRightXOffset($xOffset) {
+ $this->_bottomRightXOffset = $xOffset;
+
+ return $this;
+ }
+
+ public function getBottomRightXOffset() {
+ return $this->_bottomRightXOffset;
+ }
+
+ public function setBottomRightYOffset($yOffset) {
+ $this->_bottomRightYOffset = $yOffset;
+
+ return $this;
+ }
+
+ public function getBottomRightYOffset() {
+ return $this->_bottomRightYOffset;
+ }
+
+
+ public function refresh() {
+ if ($this->_worksheet !== NULL) {
+ $this->_plotArea->refresh($this->_worksheet);
+ }
+ }
+
+ public function render($outputDestination = null) {
+ $libraryName = PHPExcel_Settings::getChartRendererName();
+ if (is_null($libraryName)) {
+ return false;
+ }
+ // Ensure that data series values are up-to-date before we render
+ $this->refresh();
+
+ $libraryPath = PHPExcel_Settings::getChartRendererPath();
+ $includePath = str_replace('\\','/',get_include_path());
+ $rendererPath = str_replace('\\','/',$libraryPath);
+ if (strpos($rendererPath,$includePath) === false) {
+ set_include_path(get_include_path() . PATH_SEPARATOR . $libraryPath);
+ }
+
+ $rendererName = 'PHPExcel_Chart_Renderer_'.$libraryName;
+ $renderer = new $rendererName($this);
+
+ if ($outputDestination == 'php://output') {
+ $outputDestination = null;
+ }
+ return $renderer->render($outputDestination);
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Comment.php b/framework/library/phpexcel/PHPExcel/Comment.php
new file mode 100644
index 0000000..ef1818e
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Comment.php
@@ -0,0 +1,327 @@
+_author = 'Author';
+ $this->_text = new PHPExcel_RichText();
+ $this->_fillColor = new PHPExcel_Style_Color('FFFFFFE1');
+ $this->_alignment = PHPExcel_Style_Alignment::HORIZONTAL_GENERAL;
+ }
+
+ /**
+ * Get Author
+ *
+ * @return string
+ */
+ public function getAuthor() {
+ return $this->_author;
+ }
+
+ /**
+ * Set Author
+ *
+ * @param string $pValue
+ * @return PHPExcel_Comment
+ */
+ public function setAuthor($pValue = '') {
+ $this->_author = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Rich text comment
+ *
+ * @return PHPExcel_RichText
+ */
+ public function getText() {
+ return $this->_text;
+ }
+
+ /**
+ * Set Rich text comment
+ *
+ * @param PHPExcel_RichText $pValue
+ * @return PHPExcel_Comment
+ */
+ public function setText(PHPExcel_RichText $pValue) {
+ $this->_text = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get comment width (CSS style, i.e. XXpx or YYpt)
+ *
+ * @return string
+ */
+ public function getWidth() {
+ return $this->_width;
+ }
+
+ /**
+ * Set comment width (CSS style, i.e. XXpx or YYpt)
+ *
+ * @param string $value
+ * @return PHPExcel_Comment
+ */
+ public function setWidth($value = '96pt') {
+ $this->_width = $value;
+ return $this;
+ }
+
+ /**
+ * Get comment height (CSS style, i.e. XXpx or YYpt)
+ *
+ * @return string
+ */
+ public function getHeight() {
+ return $this->_height;
+ }
+
+ /**
+ * Set comment height (CSS style, i.e. XXpx or YYpt)
+ *
+ * @param string $value
+ * @return PHPExcel_Comment
+ */
+ public function setHeight($value = '55.5pt') {
+ $this->_height = $value;
+ return $this;
+ }
+
+ /**
+ * Get left margin (CSS style, i.e. XXpx or YYpt)
+ *
+ * @return string
+ */
+ public function getMarginLeft() {
+ return $this->_marginLeft;
+ }
+
+ /**
+ * Set left margin (CSS style, i.e. XXpx or YYpt)
+ *
+ * @param string $value
+ * @return PHPExcel_Comment
+ */
+ public function setMarginLeft($value = '59.25pt') {
+ $this->_marginLeft = $value;
+ return $this;
+ }
+
+ /**
+ * Get top margin (CSS style, i.e. XXpx or YYpt)
+ *
+ * @return string
+ */
+ public function getMarginTop() {
+ return $this->_marginTop;
+ }
+
+ /**
+ * Set top margin (CSS style, i.e. XXpx or YYpt)
+ *
+ * @param string $value
+ * @return PHPExcel_Comment
+ */
+ public function setMarginTop($value = '1.5pt') {
+ $this->_marginTop = $value;
+ return $this;
+ }
+
+ /**
+ * Is the comment visible by default?
+ *
+ * @return boolean
+ */
+ public function getVisible() {
+ return $this->_visible;
+ }
+
+ /**
+ * Set comment default visibility
+ *
+ * @param boolean $value
+ * @return PHPExcel_Comment
+ */
+ public function setVisible($value = false) {
+ $this->_visible = $value;
+ return $this;
+ }
+
+ /**
+ * Get fill color
+ *
+ * @return PHPExcel_Style_Color
+ */
+ public function getFillColor() {
+ return $this->_fillColor;
+ }
+
+ /**
+ * Set Alignment
+ *
+ * @param string $pValue
+ * @return PHPExcel_Comment
+ */
+ public function setAlignment($pValue = PHPExcel_Style_Alignment::HORIZONTAL_GENERAL) {
+ $this->_alignment = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Alignment
+ *
+ * @return string
+ */
+ public function getAlignment() {
+ return $this->_alignment;
+ }
+
+ /**
+ * Get hash code
+ *
+ * @return string Hash code
+ */
+ public function getHashCode() {
+ return md5(
+ $this->_author
+ . $this->_text->getHashCode()
+ . $this->_width
+ . $this->_height
+ . $this->_marginLeft
+ . $this->_marginTop
+ . ($this->_visible ? 1 : 0)
+ . $this->_fillColor->getHashCode()
+ . $this->_alignment
+ . __CLASS__
+ );
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone() {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Convert to string
+ *
+ * @return string
+ */
+ public function __toString() {
+ return $this->_text->getPlainText();
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Style/Color.php b/framework/library/phpexcel/PHPExcel/Style/Color.php
new file mode 100644
index 0000000..f473209
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Style/Color.php
@@ -0,0 +1,429 @@
+_argb = $pARGB;
+ }
+ }
+
+ /**
+ * Bind parent. Only used for supervisor
+ *
+ * @param mixed $parent
+ * @param string $parentPropertyName
+ * @return PHPExcel_Style_Color
+ */
+ public function bindParent($parent, $parentPropertyName=NULL)
+ {
+ $this->_parent = $parent;
+ $this->_parentPropertyName = $parentPropertyName;
+ return $this;
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor
+ *
+ * @return PHPExcel_Style_Color
+ */
+ public function getSharedComponent()
+ {
+ switch ($this->_parentPropertyName) {
+ case '_endColor':
+ return $this->_parent->getSharedComponent()->getEndColor(); break;
+ case '_color':
+ return $this->_parent->getSharedComponent()->getColor(); break;
+ case '_startColor':
+ return $this->_parent->getSharedComponent()->getStartColor(); break;
+ }
+ }
+
+ /**
+ * Build style array from subcomponents
+ *
+ * @param array $array
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ switch ($this->_parentPropertyName) {
+ case '_endColor':
+ $key = 'endcolor';
+ break;
+ case '_color':
+ $key = 'color';
+ break;
+ case '_startColor':
+ $key = 'startcolor';
+ break;
+
+ }
+ return $this->_parent->getStyleArray(array($key => $array));
+ }
+
+ /**
+ * Apply styles from array
+ *
+ *
+ * $objPHPExcel->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray( array('rgb' => '808080') );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Style_Color
+ */
+ public function applyFromArray($pStyles = NULL) {
+ if (is_array($pStyles)) {
+ if ($this->_isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (array_key_exists('rgb', $pStyles)) {
+ $this->setRGB($pStyles['rgb']);
+ }
+ if (array_key_exists('argb', $pStyles)) {
+ $this->setARGB($pStyles['argb']);
+ }
+ }
+ } else {
+ throw new PHPExcel_Exception("Invalid style array passed.");
+ }
+ return $this;
+ }
+
+ /**
+ * Get ARGB
+ *
+ * @return string
+ */
+ public function getARGB() {
+ if ($this->_isSupervisor) {
+ return $this->getSharedComponent()->getARGB();
+ }
+ return $this->_argb;
+ }
+
+ /**
+ * Set ARGB
+ *
+ * @param string $pValue
+ * @return PHPExcel_Style_Color
+ */
+ public function setARGB($pValue = PHPExcel_Style_Color::COLOR_BLACK) {
+ if ($pValue == '') {
+ $pValue = PHPExcel_Style_Color::COLOR_BLACK;
+ }
+ if ($this->_isSupervisor) {
+ $styleArray = $this->getStyleArray(array('argb' => $pValue));
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->_argb = $pValue;
+ }
+ return $this;
+ }
+
+ /**
+ * Get RGB
+ *
+ * @return string
+ */
+ public function getRGB() {
+ if ($this->_isSupervisor) {
+ return $this->getSharedComponent()->getRGB();
+ }
+ return substr($this->_argb, 2);
+ }
+
+ /**
+ * Set RGB
+ *
+ * @param string $pValue RGB value
+ * @return PHPExcel_Style_Color
+ */
+ public function setRGB($pValue = '000000') {
+ if ($pValue == '') {
+ $pValue = '000000';
+ }
+ if ($this->_isSupervisor) {
+ $styleArray = $this->getStyleArray(array('argb' => 'FF' . $pValue));
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->_argb = 'FF' . $pValue;
+ }
+ return $this;
+ }
+
+ /**
+ * Get a specified colour component of an RGB value
+ *
+ * @private
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param int $offset Position within the RGB value to extract
+ * @param boolean $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ * @return string The extracted colour component
+ */
+ private static function _getColourComponent($RGB,$offset,$hex=TRUE) {
+ $colour = substr($RGB, $offset, 2);
+ if (!$hex)
+ $colour = hexdec($colour);
+ return $colour;
+ }
+
+ /**
+ * Get the red colour component of an RGB value
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param boolean $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ * @return string The red colour component
+ */
+ public static function getRed($RGB,$hex=TRUE) {
+ return self::_getColourComponent($RGB, strlen($RGB) - 6, $hex);
+ }
+
+ /**
+ * Get the green colour component of an RGB value
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param boolean $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ * @return string The green colour component
+ */
+ public static function getGreen($RGB,$hex=TRUE) {
+ return self::_getColourComponent($RGB, strlen($RGB) - 4, $hex);
+ }
+
+ /**
+ * Get the blue colour component of an RGB value
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param boolean $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ * @return string The blue colour component
+ */
+ public static function getBlue($RGB,$hex=TRUE) {
+ return self::_getColourComponent($RGB, strlen($RGB) - 2, $hex);
+ }
+
+ /**
+ * Adjust the brightness of a color
+ *
+ * @param string $hex The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
+ * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ */
+ public static function changeBrightness($hex, $adjustPercentage) {
+ $rgba = (strlen($hex) == 8);
+
+ $red = self::getRed($hex, FALSE);
+ $green = self::getGreen($hex, FALSE);
+ $blue = self::getBlue($hex, FALSE);
+ if ($adjustPercentage > 0) {
+ $red += (255 - $red) * $adjustPercentage;
+ $green += (255 - $green) * $adjustPercentage;
+ $blue += (255 - $blue) * $adjustPercentage;
+ } else {
+ $red += $red * $adjustPercentage;
+ $green += $green * $adjustPercentage;
+ $blue += $blue * $adjustPercentage;
+ }
+
+ if ($red < 0) $red = 0;
+ elseif ($red > 255) $red = 255;
+ if ($green < 0) $green = 0;
+ elseif ($green > 255) $green = 255;
+ if ($blue < 0) $blue = 0;
+ elseif ($blue > 255) $blue = 255;
+
+ $rgb = strtoupper( str_pad(dechex($red), 2, '0', 0) .
+ str_pad(dechex($green), 2, '0', 0) .
+ str_pad(dechex($blue), 2, '0', 0)
+ );
+ return (($rgba) ? 'FF' : '') . $rgb;
+ }
+
+ /**
+ * Get indexed color
+ *
+ * @param int $pIndex Index entry point into the colour array
+ * @param boolean $background Flag to indicate whether default background or foreground colour
+ * should be returned if the indexed colour doesn't exist
+ * @return PHPExcel_Style_Color
+ */
+ public static function indexedColor($pIndex, $background=FALSE) {
+ // Clean parameter
+ $pIndex = intval($pIndex);
+
+ // Indexed colors
+ if (is_null(self::$_indexedColors)) {
+ self::$_indexedColors = array(
+ 1 => 'FF000000', // System Colour #1 - Black
+ 2 => 'FFFFFFFF', // System Colour #2 - White
+ 3 => 'FFFF0000', // System Colour #3 - Red
+ 4 => 'FF00FF00', // System Colour #4 - Green
+ 5 => 'FF0000FF', // System Colour #5 - Blue
+ 6 => 'FFFFFF00', // System Colour #6 - Yellow
+ 7 => 'FFFF00FF', // System Colour #7- Magenta
+ 8 => 'FF00FFFF', // System Colour #8- Cyan
+ 9 => 'FF800000', // Standard Colour #9
+ 10 => 'FF008000', // Standard Colour #10
+ 11 => 'FF000080', // Standard Colour #11
+ 12 => 'FF808000', // Standard Colour #12
+ 13 => 'FF800080', // Standard Colour #13
+ 14 => 'FF008080', // Standard Colour #14
+ 15 => 'FFC0C0C0', // Standard Colour #15
+ 16 => 'FF808080', // Standard Colour #16
+ 17 => 'FF9999FF', // Chart Fill Colour #17
+ 18 => 'FF993366', // Chart Fill Colour #18
+ 19 => 'FFFFFFCC', // Chart Fill Colour #19
+ 20 => 'FFCCFFFF', // Chart Fill Colour #20
+ 21 => 'FF660066', // Chart Fill Colour #21
+ 22 => 'FFFF8080', // Chart Fill Colour #22
+ 23 => 'FF0066CC', // Chart Fill Colour #23
+ 24 => 'FFCCCCFF', // Chart Fill Colour #24
+ 25 => 'FF000080', // Chart Line Colour #25
+ 26 => 'FFFF00FF', // Chart Line Colour #26
+ 27 => 'FFFFFF00', // Chart Line Colour #27
+ 28 => 'FF00FFFF', // Chart Line Colour #28
+ 29 => 'FF800080', // Chart Line Colour #29
+ 30 => 'FF800000', // Chart Line Colour #30
+ 31 => 'FF008080', // Chart Line Colour #31
+ 32 => 'FF0000FF', // Chart Line Colour #32
+ 33 => 'FF00CCFF', // Standard Colour #33
+ 34 => 'FFCCFFFF', // Standard Colour #34
+ 35 => 'FFCCFFCC', // Standard Colour #35
+ 36 => 'FFFFFF99', // Standard Colour #36
+ 37 => 'FF99CCFF', // Standard Colour #37
+ 38 => 'FFFF99CC', // Standard Colour #38
+ 39 => 'FFCC99FF', // Standard Colour #39
+ 40 => 'FFFFCC99', // Standard Colour #40
+ 41 => 'FF3366FF', // Standard Colour #41
+ 42 => 'FF33CCCC', // Standard Colour #42
+ 43 => 'FF99CC00', // Standard Colour #43
+ 44 => 'FFFFCC00', // Standard Colour #44
+ 45 => 'FFFF9900', // Standard Colour #45
+ 46 => 'FFFF6600', // Standard Colour #46
+ 47 => 'FF666699', // Standard Colour #47
+ 48 => 'FF969696', // Standard Colour #48
+ 49 => 'FF003366', // Standard Colour #49
+ 50 => 'FF339966', // Standard Colour #50
+ 51 => 'FF003300', // Standard Colour #51
+ 52 => 'FF333300', // Standard Colour #52
+ 53 => 'FF993300', // Standard Colour #53
+ 54 => 'FF993366', // Standard Colour #54
+ 55 => 'FF333399', // Standard Colour #55
+ 56 => 'FF333333' // Standard Colour #56
+ );
+ }
+
+ if (array_key_exists($pIndex, self::$_indexedColors)) {
+ return new PHPExcel_Style_Color(self::$_indexedColors[$pIndex]);
+ }
+
+ if ($background) {
+ return new PHPExcel_Style_Color('FFFFFFFF');
+ }
+ return new PHPExcel_Style_Color('FF000000');
+ }
+
+ /**
+ * Get hash code
+ *
+ * @return string Hash code
+ */
+ public function getHashCode() {
+ if ($this->_isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+ return md5(
+ $this->_argb
+ . __CLASS__
+ );
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Style/Conditional.php b/framework/library/phpexcel/PHPExcel/Style/Conditional.php
new file mode 100644
index 0000000..6411b4f
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Style/Conditional.php
@@ -0,0 +1,277 @@
+_conditionType = PHPExcel_Style_Conditional::CONDITION_NONE;
+ $this->_operatorType = PHPExcel_Style_Conditional::OPERATOR_NONE;
+ $this->_text = null;
+ $this->_condition = array();
+ $this->_style = new PHPExcel_Style(FALSE, TRUE);
+ }
+
+ /**
+ * Get Condition type
+ *
+ * @return string
+ */
+ public function getConditionType() {
+ return $this->_conditionType;
+ }
+
+ /**
+ * Set Condition type
+ *
+ * @param string $pValue PHPExcel_Style_Conditional condition type
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setConditionType($pValue = PHPExcel_Style_Conditional::CONDITION_NONE) {
+ $this->_conditionType = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Operator type
+ *
+ * @return string
+ */
+ public function getOperatorType() {
+ return $this->_operatorType;
+ }
+
+ /**
+ * Set Operator type
+ *
+ * @param string $pValue PHPExcel_Style_Conditional operator type
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setOperatorType($pValue = PHPExcel_Style_Conditional::OPERATOR_NONE) {
+ $this->_operatorType = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get text
+ *
+ * @return string
+ */
+ public function getText() {
+ return $this->_text;
+ }
+
+ /**
+ * Set text
+ *
+ * @param string $value
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setText($value = null) {
+ $this->_text = $value;
+ return $this;
+ }
+
+ /**
+ * Get Condition
+ *
+ * @deprecated Deprecated, use getConditions instead
+ * @return string
+ */
+ public function getCondition() {
+ if (isset($this->_condition[0])) {
+ return $this->_condition[0];
+ }
+
+ return '';
+ }
+
+ /**
+ * Set Condition
+ *
+ * @deprecated Deprecated, use setConditions instead
+ * @param string $pValue Condition
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setCondition($pValue = '') {
+ if (!is_array($pValue))
+ $pValue = array($pValue);
+
+ return $this->setConditions($pValue);
+ }
+
+ /**
+ * Get Conditions
+ *
+ * @return string[]
+ */
+ public function getConditions() {
+ return $this->_condition;
+ }
+
+ /**
+ * Set Conditions
+ *
+ * @param string[] $pValue Condition
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setConditions($pValue) {
+ if (!is_array($pValue))
+ $pValue = array($pValue);
+
+ $this->_condition = $pValue;
+ return $this;
+ }
+
+ /**
+ * Add Condition
+ *
+ * @param string $pValue Condition
+ * @return PHPExcel_Style_Conditional
+ */
+ public function addCondition($pValue = '') {
+ $this->_condition[] = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Style
+ *
+ * @return PHPExcel_Style
+ */
+ public function getStyle() {
+ return $this->_style;
+ }
+
+ /**
+ * Set Style
+ *
+ * @param PHPExcel_Style $pValue
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Style_Conditional
+ */
+ public function setStyle(PHPExcel_Style $pValue = null) {
+ $this->_style = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get hash code
+ *
+ * @return string Hash code
+ */
+ public function getHashCode() {
+ return md5(
+ $this->_conditionType
+ . $this->_operatorType
+ . implode(';', $this->_condition)
+ . $this->_style->getHashCode()
+ . __CLASS__
+ );
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone() {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/AutoFilter/Column.php b/framework/library/phpexcel/PHPExcel/Worksheet/AutoFilter/Column.php
new file mode 100644
index 0000000..393388b
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/AutoFilter/Column.php
@@ -0,0 +1,394 @@
+_columnIndex = $pColumn;
+ $this->_parent = $pParent;
+ }
+
+ /**
+ * Get AutoFilter Column Index
+ *
+ * @return string
+ */
+ public function getColumnIndex() {
+ return $this->_columnIndex;
+ }
+
+ /**
+ * Set AutoFilter Column Index
+ *
+ * @param string $pColumn Column (e.g. A)
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setColumnIndex($pColumn) {
+ // Uppercase coordinate
+ $pColumn = strtoupper($pColumn);
+ if ($this->_parent !== NULL) {
+ $this->_parent->testColumnInRange($pColumn);
+ }
+
+ $this->_columnIndex = $pColumn;
+
+ return $this;
+ }
+
+ /**
+ * Get this Column's AutoFilter Parent
+ *
+ * @return PHPExcel_Worksheet_AutoFilter
+ */
+ public function getParent() {
+ return $this->_parent;
+ }
+
+ /**
+ * Set this Column's AutoFilter Parent
+ *
+ * @param PHPExcel_Worksheet_AutoFilter
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setParent(PHPExcel_Worksheet_AutoFilter $pParent = NULL) {
+ $this->_parent = $pParent;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Type
+ *
+ * @return string
+ */
+ public function getFilterType() {
+ return $this->_filterType;
+ }
+
+ /**
+ * Set AutoFilter Type
+ *
+ * @param string $pFilterType
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setFilterType($pFilterType = self::AUTOFILTER_FILTERTYPE_FILTER) {
+ if (!in_array($pFilterType,self::$_filterTypes)) {
+ throw new PHPExcel_Exception('Invalid filter type for column AutoFilter.');
+ }
+
+ $this->_filterType = $pFilterType;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Multiple Rules And/Or Join
+ *
+ * @return string
+ */
+ public function getJoin() {
+ return $this->_join;
+ }
+
+ /**
+ * Set AutoFilter Multiple Rules And/Or
+ *
+ * @param string $pJoin And/Or
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setJoin($pJoin = self::AUTOFILTER_COLUMN_JOIN_OR) {
+ // Lowercase And/Or
+ $pJoin = strtolower($pJoin);
+ if (!in_array($pJoin,self::$_ruleJoins)) {
+ throw new PHPExcel_Exception('Invalid rule connection for column AutoFilter.');
+ }
+
+ $this->_join = $pJoin;
+
+ return $this;
+ }
+
+ /**
+ * Set AutoFilter Attributes
+ *
+ * @param string[] $pAttributes
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setAttributes($pAttributes = array()) {
+ $this->_attributes = $pAttributes;
+
+ return $this;
+ }
+
+ /**
+ * Set An AutoFilter Attribute
+ *
+ * @param string $pName Attribute Name
+ * @param string $pValue Attribute Value
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function setAttribute($pName, $pValue) {
+ $this->_attributes[$pName] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Column Attributes
+ *
+ * @return string
+ */
+ public function getAttributes() {
+ return $this->_attributes;
+ }
+
+ /**
+ * Get specific AutoFilter Column Attribute
+ *
+ * @param string $pName Attribute Name
+ * @return string
+ */
+ public function getAttribute($pName) {
+ if (isset($this->_attributes[$pName]))
+ return $this->_attributes[$pName];
+ return NULL;
+ }
+
+ /**
+ * Get all AutoFilter Column Rules
+ *
+ * @throws PHPExcel_Exception
+ * @return array of PHPExcel_Worksheet_AutoFilter_Column_Rule
+ */
+ public function getRules() {
+ return $this->_ruleset;
+ }
+
+ /**
+ * Get a specified AutoFilter Column Rule
+ *
+ * @param integer $pIndex Rule index in the ruleset array
+ * @return PHPExcel_Worksheet_AutoFilter_Column_Rule
+ */
+ public function getRule($pIndex) {
+ if (!isset($this->_ruleset[$pIndex])) {
+ $this->_ruleset[$pIndex] = new PHPExcel_Worksheet_AutoFilter_Column_Rule($this);
+ }
+ return $this->_ruleset[$pIndex];
+ }
+
+ /**
+ * Create a new AutoFilter Column Rule in the ruleset
+ *
+ * @return PHPExcel_Worksheet_AutoFilter_Column_Rule
+ */
+ public function createRule() {
+ $this->_ruleset[] = new PHPExcel_Worksheet_AutoFilter_Column_Rule($this);
+
+ return end($this->_ruleset);
+ }
+
+ /**
+ * Add a new AutoFilter Column Rule to the ruleset
+ *
+ * @param PHPExcel_Worksheet_AutoFilter_Column_Rule $pRule
+ * @param boolean $returnRule Flag indicating whether the rule object or the column object should be returned
+ * @return PHPExcel_Worksheet_AutoFilter_Column|PHPExcel_Worksheet_AutoFilter_Column_Rule
+ */
+ public function addRule(PHPExcel_Worksheet_AutoFilter_Column_Rule $pRule, $returnRule=TRUE) {
+ $pRule->setParent($this);
+ $this->_ruleset[] = $pRule;
+
+ return ($returnRule) ? $pRule : $this;
+ }
+
+ /**
+ * Delete a specified AutoFilter Column Rule
+ * If the number of rules is reduced to 1, then we reset And/Or logic to Or
+ *
+ * @param integer $pIndex Rule index in the ruleset array
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function deleteRule($pIndex) {
+ if (isset($this->_ruleset[$pIndex])) {
+ unset($this->_ruleset[$pIndex]);
+ // If we've just deleted down to a single rule, then reset And/Or joining to Or
+ if (count($this->_ruleset) <= 1) {
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Delete all AutoFilter Column Rules
+ *
+ * @return PHPExcel_Worksheet_AutoFilter_Column
+ */
+ public function clearRules() {
+ $this->_ruleset = array();
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+
+ return $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone() {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ if ($key == '_parent') {
+ // Detach from autofilter parent
+ $this->$key = NULL;
+ } else {
+ $this->$key = clone $value;
+ }
+ } elseif ((is_array($value)) && ($key == '_ruleset')) {
+ // The columns array of PHPExcel_Worksheet_AutoFilter objects
+ $this->$key = array();
+ foreach ($value as $k => $v) {
+ $this->$key[$k] = clone $v;
+ // attach the new cloned Rule to this new cloned Autofilter Cloned object
+ $this->$key[$k]->setParent($this);
+ }
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/CellIterator.php b/framework/library/phpexcel/PHPExcel/Worksheet/CellIterator.php
new file mode 100644
index 0000000..57a14e4
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/CellIterator.php
@@ -0,0 +1,161 @@
+_subject = $subject;
+ $this->_rowIndex = $rowIndex;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct() {
+ unset($this->_subject);
+ }
+
+ /**
+ * Rewind iterator
+ */
+ public function rewind() {
+ $this->_position = 0;
+ }
+
+ /**
+ * Current PHPExcel_Cell
+ *
+ * @return PHPExcel_Cell
+ */
+ public function current() {
+ return $this->_subject->getCellByColumnAndRow($this->_position, $this->_rowIndex);
+ }
+
+ /**
+ * Current key
+ *
+ * @return int
+ */
+ public function key() {
+ return $this->_position;
+ }
+
+ /**
+ * Next value
+ */
+ public function next() {
+ ++$this->_position;
+ }
+
+ /**
+ * Are there any more PHPExcel_Cell instances available?
+ *
+ * @return boolean
+ */
+ public function valid() {
+ // columnIndexFromString() returns an index based at one,
+ // treat it as a count when comparing it to the base zero
+ // position.
+ $columnCount = PHPExcel_Cell::columnIndexFromString($this->_subject->getHighestColumn());
+
+ if ($this->_onlyExistingCells) {
+ // If we aren't looking at an existing cell, either
+ // because the first column doesn't exist or next() has
+ // been called onto a nonexistent cell, then loop until we
+ // find one, or pass the last column.
+ while ($this->_position < $columnCount &&
+ !$this->_subject->cellExistsByColumnAndRow($this->_position, $this->_rowIndex)) {
+ ++$this->_position;
+ }
+ }
+
+ return $this->_position < $columnCount;
+ }
+
+ /**
+ * Get loop only existing cells
+ *
+ * @return boolean
+ */
+ public function getIterateOnlyExistingCells() {
+ return $this->_onlyExistingCells;
+ }
+
+ /**
+ * Set the iterator to loop only existing cells
+ *
+ * @param boolean $value
+ */
+ public function setIterateOnlyExistingCells($value = true) {
+ $this->_onlyExistingCells = $value;
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/Column.php b/framework/library/phpexcel/PHPExcel/Worksheet/Column.php
new file mode 100644
index 0000000..6d3f36d
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/Column.php
@@ -0,0 +1,86 @@
+parent = $parent;
+ $this->columnIndex = $columnIndex;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ unset($this->parent);
+ }
+
+ /**
+ * Get column index
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Get cell iterator
+ *
+ * @param integer $startRow The row number at which to start iterating
+ * @param integer $endRow Optionally, the row number at which to stop iterating
+ * @return PHPExcel_Worksheet_CellIterator
+ */
+ public function getCellIterator($startRow = 1, $endRow = null)
+ {
+ return new PHPExcel_Worksheet_ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow);
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/ColumnCellIterator.php b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnCellIterator.php
new file mode 100644
index 0000000..7b8c219
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnCellIterator.php
@@ -0,0 +1,216 @@
+subject = $subject;
+ $this->columnIndex = PHPExcel_Cell::columnIndexFromString($columnIndex) - 1;
+ $this->resetEnd($endRow);
+ $this->resetStart($startRow);
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ unset($this->subject);
+ }
+
+ /**
+ * (Re)Set the start row and the current row pointer
+ *
+ * @param integer $startRow The row number at which to start iterating
+ * @return PHPExcel_Worksheet_ColumnCellIterator
+ * @throws PHPExcel_Exception
+ */
+ public function resetStart($startRow = 1)
+ {
+ $this->startRow = $startRow;
+ $this->adjustForExistingOnlyRange();
+ $this->seek($startRow);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end row
+ *
+ * @param integer $endRow The row number at which to stop iterating
+ * @return PHPExcel_Worksheet_ColumnCellIterator
+ * @throws PHPExcel_Exception
+ */
+ public function resetEnd($endRow = null)
+ {
+ $this->endRow = ($endRow) ? $endRow : $this->subject->getHighestRow();
+ $this->adjustForExistingOnlyRange();
+
+ return $this;
+ }
+
+ /**
+ * Set the row pointer to the selected row
+ *
+ * @param integer $row The row number to set the current pointer at
+ * @return PHPExcel_Worksheet_ColumnCellIterator
+ * @throws PHPExcel_Exception
+ */
+ public function seek($row = 1)
+ {
+ if (($row < $this->startRow) || ($row > $this->endRow)) {
+ throw new PHPExcel_Exception("Row $row is out of range ({$this->startRow} - {$this->endRow})");
+ } elseif ($this->onlyExistingCells && !($this->subject->cellExistsByColumnAndRow($this->columnIndex, $row))) {
+ throw new PHPExcel_Exception('In "IterateOnlyExistingCells" mode and Cell does not exist');
+ }
+ $this->position = $row;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting row
+ */
+ public function rewind()
+ {
+ $this->position = $this->startRow;
+ }
+
+ /**
+ * Return the current cell in this worksheet column
+ *
+ * @return PHPExcel_Worksheet_Row
+ */
+ public function current()
+ {
+ return $this->subject->getCellByColumnAndRow($this->columnIndex, $this->position);
+ }
+
+ /**
+ * Return the current iterator key
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Set the iterator to its next value
+ */
+ public function next()
+ {
+ do {
+ ++$this->position;
+ } while (($this->onlyExistingCells) &&
+ (!$this->subject->cellExistsByColumnAndRow($this->columnIndex, $this->position)) &&
+ ($this->position <= $this->endRow));
+ }
+
+ /**
+ * Set the iterator to its previous value
+ */
+ public function prev()
+ {
+ if ($this->position <= $this->startRow) {
+ throw new PHPExcel_Exception("Row is already at the beginning of range ({$this->startRow} - {$this->endRow})");
+ }
+
+ do {
+ --$this->position;
+ } while (($this->onlyExistingCells) &&
+ (!$this->subject->cellExistsByColumnAndRow($this->columnIndex, $this->position)) &&
+ ($this->position >= $this->startRow));
+ }
+
+ /**
+ * Indicate if more rows exist in the worksheet range of rows that we're iterating
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->position <= $this->endRow;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary
+ *
+ * @throws PHPExcel_Exception
+ */
+ protected function adjustForExistingOnlyRange()
+ {
+ if ($this->onlyExistingCells) {
+ while ((!$this->subject->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) &&
+ ($this->startRow <= $this->endRow)) {
+ ++$this->startRow;
+ }
+ if ($this->startRow > $this->endRow) {
+ throw new PHPExcel_Exception('No cells exist within the specified range');
+ }
+ while ((!$this->subject->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) &&
+ ($this->endRow >= $this->startRow)) {
+ --$this->endRow;
+ }
+ if ($this->endRow < $this->startRow) {
+ throw new PHPExcel_Exception('No cells exist within the specified range');
+ }
+ }
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/ColumnDimension.php b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnDimension.php
new file mode 100644
index 0000000..202c0f4
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnDimension.php
@@ -0,0 +1,266 @@
+_columnIndex = $pIndex;
+
+ // set default index to cellXf
+ $this->_xfIndex = 0;
+ }
+
+ /**
+ * Get ColumnIndex
+ *
+ * @return string
+ */
+ public function getColumnIndex() {
+ return $this->_columnIndex;
+ }
+
+ /**
+ * Set ColumnIndex
+ *
+ * @param string $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setColumnIndex($pValue) {
+ $this->_columnIndex = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Width
+ *
+ * @return double
+ */
+ public function getWidth() {
+ return $this->_width;
+ }
+
+ /**
+ * Set Width
+ *
+ * @param double $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setWidth($pValue = -1) {
+ $this->_width = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Auto Size
+ *
+ * @return bool
+ */
+ public function getAutoSize() {
+ return $this->_autoSize;
+ }
+
+ /**
+ * Set Auto Size
+ *
+ * @param bool $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setAutoSize($pValue = false) {
+ $this->_autoSize = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Visible
+ *
+ * @return bool
+ */
+ public function getVisible() {
+ return $this->_visible;
+ }
+
+ /**
+ * Set Visible
+ *
+ * @param bool $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setVisible($pValue = true) {
+ $this->_visible = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Outline Level
+ *
+ * @return int
+ */
+ public function getOutlineLevel() {
+ return $this->_outlineLevel;
+ }
+
+ /**
+ * Set Outline Level
+ *
+ * Value must be between 0 and 7
+ *
+ * @param int $pValue
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setOutlineLevel($pValue) {
+ if ($pValue < 0 || $pValue > 7) {
+ throw new PHPExcel_Exception("Outline level must range between 0 and 7.");
+ }
+
+ $this->_outlineLevel = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Collapsed
+ *
+ * @return bool
+ */
+ public function getCollapsed() {
+ return $this->_collapsed;
+ }
+
+ /**
+ * Set Collapsed
+ *
+ * @param bool $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setCollapsed($pValue = true) {
+ $this->_collapsed = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get index to cellXf
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->_xfIndex;
+ }
+
+ /**
+ * Set index to cellXf
+ *
+ * @param int $pValue
+ * @return PHPExcel_Worksheet_ColumnDimension
+ */
+ public function setXfIndex($pValue = 0)
+ {
+ $this->_xfIndex = $pValue;
+ return $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone() {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/ColumnIterator.php b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnIterator.php
new file mode 100644
index 0000000..0db3e53
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/ColumnIterator.php
@@ -0,0 +1,201 @@
+subject = $subject;
+ $this->resetEnd($endColumn);
+ $this->resetStart($startColumn);
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ unset($this->subject);
+ }
+
+ /**
+ * (Re)Set the start column and the current column pointer
+ *
+ * @param integer $startColumn The column address at which to start iterating
+ * @return PHPExcel_Worksheet_ColumnIterator
+ * @throws PHPExcel_Exception
+ */
+ public function resetStart($startColumn = 'A')
+ {
+ $startColumnIndex = PHPExcel_Cell::columnIndexFromString($startColumn) - 1;
+ if ($startColumnIndex > PHPExcel_Cell::columnIndexFromString($this->subject->getHighestColumn()) - 1) {
+ throw new PHPExcel_Exception("Start column ({$startColumn}) is beyond highest column ({$this->subject->getHighestColumn()})");
+ }
+
+ $this->startColumn = $startColumnIndex;
+ if ($this->endColumn < $this->startColumn) {
+ $this->endColumn = $this->startColumn;
+ }
+ $this->seek($startColumn);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end column
+ *
+ * @param string $endColumn The column address at which to stop iterating
+ * @return PHPExcel_Worksheet_ColumnIterator
+ */
+ public function resetEnd($endColumn = null)
+ {
+ $endColumn = ($endColumn) ? $endColumn : $this->subject->getHighestColumn();
+ $this->endColumn = PHPExcel_Cell::columnIndexFromString($endColumn) - 1;
+
+ return $this;
+ }
+
+ /**
+ * Set the column pointer to the selected column
+ *
+ * @param string $column The column address to set the current pointer at
+ * @return PHPExcel_Worksheet_ColumnIterator
+ * @throws PHPExcel_Exception
+ */
+ public function seek($column = 'A')
+ {
+ $column = PHPExcel_Cell::columnIndexFromString($column) - 1;
+ if (($column < $this->startColumn) || ($column > $this->endColumn)) {
+ throw new PHPExcel_Exception("Column $column is out of range ({$this->startColumn} - {$this->endColumn})");
+ }
+ $this->position = $column;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting column
+ */
+ public function rewind()
+ {
+ $this->position = $this->startColumn;
+ }
+
+ /**
+ * Return the current column in this worksheet
+ *
+ * @return PHPExcel_Worksheet_Column
+ */
+ public function current()
+ {
+ return new PHPExcel_Worksheet_Column($this->subject, PHPExcel_Cell::stringFromColumnIndex($this->position));
+ }
+
+ /**
+ * Return the current iterator key
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return PHPExcel_Cell::stringFromColumnIndex($this->position);
+ }
+
+ /**
+ * Set the iterator to its next value
+ */
+ public function next()
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Set the iterator to its previous value
+ *
+ * @throws PHPExcel_Exception
+ */
+ public function prev()
+ {
+ if ($this->position <= $this->startColumn) {
+ throw new PHPExcel_Exception(
+ "Column is already at the beginning of range (" .
+ PHPExcel_Cell::stringFromColumnIndex($this->endColumn) . " - " .
+ PHPExcel_Cell::stringFromColumnIndex($this->endColumn) . ")"
+ );
+ }
+
+ --$this->position;
+ }
+
+ /**
+ * Indicate if more columns exist in the worksheet range of columns that we're iterating
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->position <= $this->endColumn;
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Worksheet/Dimension.php b/framework/library/phpexcel/PHPExcel/Worksheet/Dimension.php
new file mode 100644
index 0000000..84f692c
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Worksheet/Dimension.php
@@ -0,0 +1,178 @@
+xfIndex = $initialValue;
+ }
+
+ /**
+ * Get Visible
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set Visible
+ *
+ * @param bool $pValue
+ * @return PHPExcel_Worksheet_Dimension
+ */
+ public function setVisible($pValue = true)
+ {
+ $this->visible = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Outline Level
+ *
+ * @return int
+ */
+ public function getOutlineLevel()
+ {
+ return $this->outlineLevel;
+ }
+
+ /**
+ * Set Outline Level
+ *
+ * Value must be between 0 and 7
+ *
+ * @param int $pValue
+ * @throws PHPExcel_Exception
+ * @return PHPExcel_Worksheet_Dimension
+ */
+ public function setOutlineLevel($pValue)
+ {
+ if ($pValue < 0 || $pValue > 7) {
+ throw new PHPExcel_Exception("Outline level must range between 0 and 7.");
+ }
+
+ $this->outlineLevel = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Collapsed
+ *
+ * @return bool
+ */
+ public function getCollapsed()
+ {
+ return $this->collapsed;
+ }
+
+ /**
+ * Set Collapsed
+ *
+ * @param bool $pValue
+ * @return PHPExcel_Worksheet_Dimension
+ */
+ public function setCollapsed($pValue = true)
+ {
+ $this->collapsed = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get index to cellXf
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->xfIndex;
+ }
+
+ /**
+ * Set index to cellXf
+ *
+ * @param int $pValue
+ * @return PHPExcel_Worksheet_Dimension
+ */
+ public function setXfIndex($pValue = 0)
+ {
+ $this->xfIndex = $pValue;
+ return $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/CSV.php b/framework/library/phpexcel/PHPExcel/Writer/CSV.php
new file mode 100644
index 0000000..0770c1d
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/CSV.php
@@ -0,0 +1,313 @@
+_phpExcel = $phpExcel;
+ }
+
+ /**
+ * Save PHPExcel to file
+ *
+ * @param string $pFilename
+ * @throws PHPExcel_Writer_Exception
+ */
+ public function save($pFilename = null) {
+ // Fetch sheet
+ $sheet = $this->_phpExcel->getSheet($this->_sheetIndex);
+
+ $saveDebugLog = PHPExcel_Calculation::getInstance($this->_phpExcel)->getDebugLog()->getWriteDebugLog();
+ PHPExcel_Calculation::getInstance($this->_phpExcel)->getDebugLog()->setWriteDebugLog(FALSE);
+ $saveArrayReturnType = PHPExcel_Calculation::getArrayReturnType();
+ PHPExcel_Calculation::setArrayReturnType(PHPExcel_Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Open file
+ $fileHandle = fopen($pFilename, 'wb+');
+ if ($fileHandle === false) {
+ throw new PHPExcel_Writer_Exception("Could not open file $pFilename for writing.");
+ }
+
+ if ($this->_excelCompatibility) {
+ // Write the UTF-16LE BOM code
+ fwrite($fileHandle, "\xFF\xFE"); // Excel uses UTF-16LE encoding
+ $this->setEnclosure(); // Default enclosure is "
+ $this->setDelimiter("\t"); // Excel delimiter is a TAB
+ } elseif ($this->_useBOM) {
+ // Write the UTF-8 BOM code
+ fwrite($fileHandle, "\xEF\xBB\xBF");
+ }
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $sheet->getHighestColumn();
+ $maxRow = $sheet->getHighestRow();
+
+ // Write rows to file
+ for($row = 1; $row <= $maxRow; ++$row) {
+ // Convert the row to an array...
+ $cellsArray = $sheet->rangeToArray('A'.$row.':'.$maxCol.$row,'', $this->_preCalculateFormulas);
+ // ... and write to the file
+ $this->_writeLine($fileHandle, $cellsArray[0]);
+ }
+
+ // Close file
+ fclose($fileHandle);
+
+ PHPExcel_Calculation::setArrayReturnType($saveArrayReturnType);
+ PHPExcel_Calculation::getInstance($this->_phpExcel)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Get delimiter
+ *
+ * @return string
+ */
+ public function getDelimiter() {
+ return $this->_delimiter;
+ }
+
+ /**
+ * Set delimiter
+ *
+ * @param string $pValue Delimiter, defaults to ,
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setDelimiter($pValue = ',') {
+ $this->_delimiter = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get enclosure
+ *
+ * @return string
+ */
+ public function getEnclosure() {
+ return $this->_enclosure;
+ }
+
+ /**
+ * Set enclosure
+ *
+ * @param string $pValue Enclosure, defaults to "
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setEnclosure($pValue = '"') {
+ if ($pValue == '') {
+ $pValue = null;
+ }
+ $this->_enclosure = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get line ending
+ *
+ * @return string
+ */
+ public function getLineEnding() {
+ return $this->_lineEnding;
+ }
+
+ /**
+ * Set line ending
+ *
+ * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL)
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setLineEnding($pValue = PHP_EOL) {
+ $this->_lineEnding = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get whether BOM should be used
+ *
+ * @return boolean
+ */
+ public function getUseBOM() {
+ return $this->_useBOM;
+ }
+
+ /**
+ * Set whether BOM should be used
+ *
+ * @param boolean $pValue Use UTF-8 byte-order mark? Defaults to false
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setUseBOM($pValue = false) {
+ $this->_useBOM = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get whether the file should be saved with full Excel Compatibility
+ *
+ * @return boolean
+ */
+ public function getExcelCompatibility() {
+ return $this->_excelCompatibility;
+ }
+
+ /**
+ * Set whether the file should be saved with full Excel Compatibility
+ *
+ * @param boolean $pValue Set the file to be written as a fully Excel compatible csv file
+ * Note that this overrides other settings such as useBOM, enclosure and delimiter
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setExcelCompatibility($pValue = false) {
+ $this->_excelCompatibility = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get sheet index
+ *
+ * @return int
+ */
+ public function getSheetIndex() {
+ return $this->_sheetIndex;
+ }
+
+ /**
+ * Set sheet index
+ *
+ * @param int $pValue Sheet index
+ * @return PHPExcel_Writer_CSV
+ */
+ public function setSheetIndex($pValue = 0) {
+ $this->_sheetIndex = $pValue;
+ return $this;
+ }
+
+ /**
+ * Write line to CSV file
+ *
+ * @param mixed $pFileHandle PHP filehandle
+ * @param array $pValues Array containing values in a row
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeLine($pFileHandle = null, $pValues = null) {
+ if (is_array($pValues)) {
+ // No leading delimiter
+ $writeDelimiter = false;
+
+ // Build the line
+ $line = '';
+
+ foreach ($pValues as $element) {
+ // Escape enclosures
+ $element = str_replace($this->_enclosure, $this->_enclosure . $this->_enclosure, $element);
+
+ // Add delimiter
+ if ($writeDelimiter) {
+ $line .= $this->_delimiter;
+ } else {
+ $writeDelimiter = true;
+ }
+
+ // Add enclosed string
+ $line .= $this->_enclosure . $element . $this->_enclosure;
+ }
+
+ // Add line ending
+ $line .= $this->_lineEnding;
+
+ // Write to file
+ if ($this->_excelCompatibility) {
+ fwrite($pFileHandle, mb_convert_encoding($line,"UTF-16LE","UTF-8"));
+ } else {
+ fwrite($pFileHandle, $line);
+ }
+ } else {
+ throw new PHPExcel_Writer_Exception("Invalid data row passed to CSV writer.");
+ }
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Chart.php b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Chart.php
new file mode 100644
index 0000000..5adfb7c
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Chart.php
@@ -0,0 +1,1184 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+ }
+ // Ensure that data series values are up-to-date before we save
+ $pChart->refresh();
+
+ // XML header
+ $objWriter->startDocument('1.0','UTF-8','yes');
+
+ // c:chartSpace
+ $objWriter->startElement('c:chartSpace');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->startElement('c:date1904');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ $objWriter->startElement('c:lang');
+ $objWriter->writeAttribute('val', "en-GB");
+ $objWriter->endElement();
+ $objWriter->startElement('c:roundedCorners');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->_writeAlternateContent($objWriter);
+
+ $objWriter->startElement('c:chart');
+
+ $this->_writeTitle($pChart->getTitle(), $objWriter);
+
+ $objWriter->startElement('c:autoTitleDeleted');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->_writePlotArea($pChart->getPlotArea(),
+ $pChart->getXAxisLabel(),
+ $pChart->getYAxisLabel(),
+ $objWriter,
+ $pChart->getWorksheet()
+ );
+
+ $this->_writeLegend($pChart->getLegend(), $objWriter);
+
+
+ $objWriter->startElement('c:plotVisOnly');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:dispBlanksAs');
+ $objWriter->writeAttribute('val', "gap");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showDLblsOverMax');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $this->_writePrintSettings($objWriter);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Chart Title
+ *
+ * @param PHPExcel_Chart_Title $title
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeTitle(PHPExcel_Chart_Title $title = null, $objWriter)
+ {
+ if (is_null($title)) {
+ return;
+ }
+
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+
+ $caption = $title->getCaption();
+ if ((is_array($caption)) && (count($caption) > 0))
+ $caption = $caption[0];
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a');
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $layout = $title->getLayout();
+ $this->_writeLayout($layout, $objWriter);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Legend
+ *
+ * @param PHPExcel_Chart_Legend $legend
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeLegend(PHPExcel_Chart_Legend $legend = null, $objWriter)
+ {
+ if (is_null($legend)) {
+ return;
+ }
+
+ $objWriter->startElement('c:legend');
+
+ $objWriter->startElement('c:legendPos');
+ $objWriter->writeAttribute('val', $legend->getPosition());
+ $objWriter->endElement();
+
+ $layout = $legend->getLayout();
+ $this->_writeLayout($layout, $objWriter);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:txPr');
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->writeAttribute('rtl', 0);
+
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:endParaRPr');
+ $objWriter->writeAttribute('lang', "en-US");
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Plot Area
+ *
+ * @param PHPExcel_Chart_PlotArea $plotArea
+ * @param PHPExcel_Chart_Title $xAxisLabel
+ * @param PHPExcel_Chart_Title $yAxisLabel
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writePlotArea(PHPExcel_Chart_PlotArea $plotArea,
+ PHPExcel_Chart_Title $xAxisLabel = NULL,
+ PHPExcel_Chart_Title $yAxisLabel = NULL,
+ $objWriter,
+ PHPExcel_Worksheet $pSheet)
+ {
+ if (is_null($plotArea)) {
+ return;
+ }
+
+ $id1 = $id2 = 0;
+ $this->_seriesIndex = 0;
+ $objWriter->startElement('c:plotArea');
+
+ $layout = $plotArea->getLayout();
+
+ $this->_writeLayout($layout, $objWriter);
+
+ $chartTypes = self::_getChartType($plotArea);
+ $catIsMultiLevelSeries = $valIsMultiLevelSeries = FALSE;
+ $plotGroupingType = '';
+ foreach($chartTypes as $chartType) {
+ $objWriter->startElement('c:'.$chartType);
+
+ $groupCount = $plotArea->getPlotGroupCount();
+ for($i = 0; $i < $groupCount; ++$i) {
+ $plotGroup = $plotArea->getPlotGroupByIndex($i);
+ $groupType = $plotGroup->getPlotType();
+ if ($groupType == $chartType) {
+
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($groupType === PHPExcel_Chart_DataSeries::TYPE_RADARCHART) {
+ $objWriter->startElement('c:radarStyle');
+ $objWriter->writeAttribute('val', $plotStyle );
+ $objWriter->endElement();
+ } elseif ($groupType === PHPExcel_Chart_DataSeries::TYPE_SCATTERCHART) {
+ $objWriter->startElement('c:scatterStyle');
+ $objWriter->writeAttribute('val', $plotStyle );
+ $objWriter->endElement();
+ }
+
+ $this->_writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType, $pSheet);
+ }
+ }
+
+ $this->_writeDataLbls($objWriter, $layout);
+
+ if ($chartType === PHPExcel_Chart_DataSeries::TYPE_LINECHART) {
+ // Line only, Line3D can't be smoothed
+
+ $objWriter->startElement('c:smooth');
+ $objWriter->writeAttribute('val', (integer) $plotGroup->getSmoothLine() );
+ $objWriter->endElement();
+ } elseif (($chartType === PHPExcel_Chart_DataSeries::TYPE_BARCHART) ||
+ ($chartType === PHPExcel_Chart_DataSeries::TYPE_BARCHART_3D)) {
+
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 150 );
+ $objWriter->endElement();
+
+ if ($plotGroupingType == 'percentStacked' ||
+ $plotGroupingType == 'stacked') {
+
+ $objWriter->startElement('c:overlap');
+ $objWriter->writeAttribute('val', 100 );
+ $objWriter->endElement();
+ }
+ } elseif ($chartType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) {
+
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', 25 );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showNegBubbles');
+ $objWriter->writeAttribute('val', 0 );
+ $objWriter->endElement();
+ } elseif ($chartType === PHPExcel_Chart_DataSeries::TYPE_STOCKCHART) {
+
+ $objWriter->startElement('c:hiLowLines');
+ $objWriter->endElement();
+ }
+
+ // Generate 2 unique numbers to use for axId values
+// $id1 = $id2 = rand(10000000,99999999);
+// do {
+// $id2 = rand(10000000,99999999);
+// } while ($id1 == $id2);
+ $id1 = '75091328';
+ $id2 = '75089408';
+
+ if (($chartType !== PHPExcel_Chart_DataSeries::TYPE_PIECHART) &&
+ ($chartType !== PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) &&
+ ($chartType !== PHPExcel_Chart_DataSeries::TYPE_DONUTCHART)) {
+
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1 );
+ $objWriter->endElement();
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2 );
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:firstSliceAng');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ if ($chartType === PHPExcel_Chart_DataSeries::TYPE_DONUTCHART) {
+
+ $objWriter->startElement('c:holeSize');
+ $objWriter->writeAttribute('val', 50);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ if (($chartType !== PHPExcel_Chart_DataSeries::TYPE_PIECHART) &&
+ ($chartType !== PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) &&
+ ($chartType !== PHPExcel_Chart_DataSeries::TYPE_DONUTCHART)) {
+
+ if ($chartType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) {
+ $this->_writeValAx($objWriter,$plotArea,$xAxisLabel,$chartType,$id1,$id2,$catIsMultiLevelSeries);
+ } else {
+ $this->_writeCatAx($objWriter,$plotArea,$xAxisLabel,$chartType,$id1,$id2,$catIsMultiLevelSeries);
+ }
+
+ $this->_writeValAx($objWriter,$plotArea,$yAxisLabel,$chartType,$id1,$id2,$valIsMultiLevelSeries);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Data Labels
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param PHPExcel_Chart_Layout $chartLayout Chart layout
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeDataLbls($objWriter, $chartLayout)
+ {
+ $objWriter->startElement('c:dLbls');
+
+ $objWriter->startElement('c:showLegendKey');
+ $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey();
+ $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1) );
+ $objWriter->endElement();
+
+
+ $objWriter->startElement('c:showVal');
+ $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal();
+ $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showCatName');
+ $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName();
+ $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showSerName');
+ $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName();
+ $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showPercent');
+ $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent();
+ $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showBubbleSize');
+ $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize();
+ $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showLeaderLines');
+ $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines();
+ $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1) );
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Category Axis
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param PHPExcel_Chart_PlotArea $plotArea
+ * @param PHPExcel_Chart_Title $xAxisLabel
+ * @param string $groupType Chart type
+ * @param string $id1
+ * @param string $id2
+ * @param boolean $isMultiLevelSeries
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeCatAx($objWriter, PHPExcel_Chart_PlotArea $plotArea, $xAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries)
+ {
+ $objWriter->startElement('c:catAx');
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', "minMax");
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', "b");
+ $objWriter->endElement();
+
+ if (!is_null($xAxisLabel)) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $xAxisLabel->getCaption();
+ if (is_array($caption))
+ $caption = $caption[0];
+ $objWriter->startElement('a:t');
+// $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeRawData(PHPExcel_Shared_String::ControlCharacterPHP2OOXML( $caption ));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $layout = $xAxisLabel->getLayout();
+ $this->_writeLayout($layout, $objWriter);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', "General");
+ $objWriter->writeAttribute('sourceLinked', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', "out");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', "none");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', "nextTo");
+ $objWriter->endElement();
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', "autoZero");
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:auto');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblAlgn');
+ $objWriter->writeAttribute('val', "ctr");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblOffset');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+
+ if ($isMultiLevelSeries) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+
+ }
+
+
+ /**
+ * Write Value Axis
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param PHPExcel_Chart_PlotArea $plotArea
+ * @param PHPExcel_Chart_Title $yAxisLabel
+ * @param string $groupType Chart type
+ * @param string $id1
+ * @param string $id2
+ * @param boolean $isMultiLevelSeries
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeValAx($objWriter, PHPExcel_Chart_PlotArea $plotArea, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries)
+ {
+ $objWriter->startElement('c:valAx');
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', "minMax");
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', "l");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorGridlines');
+ $objWriter->endElement();
+
+ if (!is_null($yAxisLabel)) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $yAxisLabel->getCaption();
+ if (is_array($caption))
+ $caption = $caption[0];
+ $objWriter->startElement('a:t');
+// $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeRawData(PHPExcel_Shared_String::ControlCharacterPHP2OOXML( $caption ));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ if ($groupType !== PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) {
+ $layout = $yAxisLabel->getLayout();
+ $this->_writeLayout($layout, $objWriter);
+ }
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', "General");
+ $objWriter->writeAttribute('sourceLinked', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', "out");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', "none");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', "nextTo");
+ $objWriter->endElement();
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', "autoZero");
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crossBetween');
+ $objWriter->writeAttribute('val', "midCat");
+ $objWriter->endElement();
+ }
+
+ if ($isMultiLevelSeries) {
+ if ($groupType !== PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+ $objWriter->endElement();
+
+ }
+
+
+ /**
+ * Get the data series type(s) for a chart plot series
+ *
+ * @param PHPExcel_Chart_PlotArea $plotArea
+ * @return string|array
+ * @throws PHPExcel_Writer_Exception
+ */
+ private static function _getChartType($plotArea)
+ {
+ $groupCount = $plotArea->getPlotGroupCount();
+
+ if ($groupCount == 1) {
+ $chartType = array($plotArea->getPlotGroupByIndex(0)->getPlotType());
+ } else {
+ $chartTypes = array();
+ for($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartType = array_unique($chartTypes);
+ if (count($chartTypes) == 0) {
+ throw new PHPExcel_Writer_Exception('Chart is not yet implemented');
+ }
+ }
+
+ return $chartType;
+ }
+
+ /**
+ * Write Plot Group (series of related plots)
+ *
+ * @param PHPExcel_Chart_DataSeries $plotGroup
+ * @param string $groupType Type of plot for dataseries
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param boolean &$catIsMultiLevelSeries Is category a multi-series category
+ * @param boolean &$valIsMultiLevelSeries Is value set a multi-series set
+ * @param string &$plotGroupingType Type of grouping for multi-series values
+ * @param PHPExcel_Worksheet $pSheet
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writePlotGroup( $plotGroup,
+ $groupType,
+ $objWriter,
+ &$catIsMultiLevelSeries,
+ &$valIsMultiLevelSeries,
+ &$plotGroupingType,
+ PHPExcel_Worksheet $pSheet
+ )
+ {
+ if (is_null($plotGroup)) {
+ return;
+ }
+
+ if (($groupType == PHPExcel_Chart_DataSeries::TYPE_BARCHART) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:barDir');
+ $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
+ $objWriter->endElement();
+ }
+
+ if (!is_null($plotGroup->getPlotGrouping())) {
+ $plotGroupingType = $plotGroup->getPlotGrouping();
+ $objWriter->startElement('c:grouping');
+ $objWriter->writeAttribute('val', $plotGroupingType);
+ $objWriter->endElement();
+ }
+
+ // Get these details before the loop, because we can use the count to check for varyColors
+ $plotSeriesOrder = $plotGroup->getPlotOrder();
+ $plotSeriesCount = count($plotSeriesOrder);
+
+ if (($groupType !== PHPExcel_Chart_DataSeries::TYPE_RADARCHART) &&
+ ($groupType !== PHPExcel_Chart_DataSeries::TYPE_STOCKCHART)) {
+
+ if ($groupType !== PHPExcel_Chart_DataSeries::TYPE_LINECHART) {
+ if (($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_DONUTCHART) ||
+ ($plotSeriesCount > 1)) {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ foreach($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
+ $objWriter->startElement('c:ser');
+
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $this->_seriesIndex + $plotSeriesIdx);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:order');
+ $objWriter->writeAttribute('val', $this->_seriesIndex + $plotSeriesRef);
+ $objWriter->endElement();
+
+ if (($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_DONUTCHART)) {
+
+ $objWriter->startElement('c:dPt');
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', 3);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', 'FF9900');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ // Labels
+ $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef);
+ if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:strRef');
+ $this->_writePlotSeriesLabel($plotSeriesLabel, $objWriter);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ // Formatting for the points
+ if ($groupType == PHPExcel_Chart_DataSeries::TYPE_LINECHART) {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', 12700);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
+ if ($plotSeriesValues) {
+ $plotSeriesMarker = $plotSeriesValues->getPointMarker();
+ if ($plotSeriesMarker) {
+ $objWriter->startElement('c:marker');
+ $objWriter->startElement('c:symbol');
+ $objWriter->writeAttribute('val', $plotSeriesMarker);
+ $objWriter->endElement();
+
+ if ($plotSeriesMarker !== 'none') {
+ $objWriter->startElement('c:size');
+ $objWriter->writeAttribute('val', 3);
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ if (($groupType === PHPExcel_Chart_DataSeries::TYPE_BARCHART) ||
+ ($groupType === PHPExcel_Chart_DataSeries::TYPE_BARCHART_3D) ||
+ ($groupType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART)) {
+
+ $objWriter->startElement('c:invertIfNegative');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ // Category Labels
+ $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef);
+ if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
+ $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
+
+ if (($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) ||
+ ($groupType == PHPExcel_Chart_DataSeries::TYPE_DONUTCHART)) {
+
+ if (!is_null($plotGroup->getPlotStyle())) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($plotStyle) {
+ $objWriter->startElement('c:explosion');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ if (($groupType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) ||
+ ($groupType === PHPExcel_Chart_DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:xVal');
+ } else {
+ $objWriter->startElement('c:cat');
+ }
+
+ $this->_writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str', $pSheet);
+ $objWriter->endElement();
+ }
+
+ // Values
+ if ($plotSeriesValues) {
+ $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
+
+ if (($groupType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) ||
+ ($groupType === PHPExcel_Chart_DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:yVal');
+ } else {
+ $objWriter->startElement('c:val');
+ }
+
+ $this->_writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num', $pSheet);
+ $objWriter->endElement();
+ }
+
+ if ($groupType === PHPExcel_Chart_DataSeries::TYPE_BUBBLECHART) {
+ $this->_writeBubbles($plotSeriesValues, $objWriter, $pSheet);
+ }
+
+ $objWriter->endElement();
+
+ }
+
+ $this->_seriesIndex += $plotSeriesIdx + 1;
+ }
+
+ /**
+ * Write Plot Series Label
+ *
+ * @param PHPExcel_Chart_DataSeriesValues $plotSeriesLabel
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writePlotSeriesLabel($plotSeriesLabel, $objWriter)
+ {
+ if (is_null($plotSeriesLabel)) {
+ return;
+ }
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesLabel->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:strCache');
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount() );
+ $objWriter->endElement();
+
+ foreach($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotLabelKey );
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData( $plotLabelValue );
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+
+ }
+
+ /**
+ * Write Plot Series Values
+ *
+ * @param PHPExcel_Chart_DataSeriesValues $plotSeriesValues
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param string $groupType Type of plot for dataseries
+ * @param string $dataType Datatype of series values
+ * @param PHPExcel_Worksheet $pSheet
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writePlotSeriesValues( $plotSeriesValues,
+ $objWriter,
+ $groupType,
+ $dataType='str',
+ PHPExcel_Worksheet $pSheet
+ )
+ {
+ if (is_null($plotSeriesValues)) {
+ return;
+ }
+
+ if ($plotSeriesValues->isMultiLevelSeries()) {
+ $levelCount = $plotSeriesValues->multiLevelCount();
+
+ $objWriter->startElement('c:multiLvlStrRef');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData( $plotSeriesValues->getDataSource() );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:multiLvlStrCache');
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount() );
+ $objWriter->endElement();
+
+ for ($level = 0; $level < $levelCount; ++$level) {
+ $objWriter->startElement('c:lvl');
+
+ foreach($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
+ if (isset($plotSeriesValue[$level])) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey );
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData( $plotSeriesValue[$level] );
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:'.$dataType.'Ref');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData( $plotSeriesValues->getDataSource() );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:'.$dataType.'Cache');
+
+ if (($groupType != PHPExcel_Chart_DataSeries::TYPE_PIECHART) &&
+ ($groupType != PHPExcel_Chart_DataSeries::TYPE_PIECHART_3D) &&
+ ($groupType != PHPExcel_Chart_DataSeries::TYPE_DONUTCHART)) {
+
+ if (($plotSeriesValues->getFormatCode() !== NULL) &&
+ ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData( $plotSeriesValues->getFormatCode() );
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount() );
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey );
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData( $plotSeriesValue );
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Bubble Chart Details
+ *
+ * @param PHPExcel_Chart_DataSeriesValues $plotSeriesValues
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeBubbles($plotSeriesValues, $objWriter, PHPExcel_Worksheet $pSheet)
+ {
+ if (is_null($plotSeriesValues)) {
+ return;
+ }
+
+ $objWriter->startElement('c:bubbleSize');
+ $objWriter->startElement('c:numLit');
+
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData( 'General' );
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount() );
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey );
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData( 1 );
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0 );
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Layout
+ *
+ * @param PHPExcel_Chart_Layout $layout
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeLayout(PHPExcel_Chart_Layout $layout = NULL, $objWriter)
+ {
+ $objWriter->startElement('c:layout');
+
+ if (!is_null($layout)) {
+ $objWriter->startElement('c:manualLayout');
+
+ $layoutTarget = $layout->getLayoutTarget();
+ if (!is_null($layoutTarget)) {
+ $objWriter->startElement('c:layoutTarget');
+ $objWriter->writeAttribute('val', $layoutTarget);
+ $objWriter->endElement();
+ }
+
+ $xMode = $layout->getXMode();
+ if (!is_null($xMode)) {
+ $objWriter->startElement('c:xMode');
+ $objWriter->writeAttribute('val', $xMode);
+ $objWriter->endElement();
+ }
+
+ $yMode = $layout->getYMode();
+ if (!is_null($yMode)) {
+ $objWriter->startElement('c:yMode');
+ $objWriter->writeAttribute('val', $yMode);
+ $objWriter->endElement();
+ }
+
+ $x = $layout->getXPosition();
+ if (!is_null($x)) {
+ $objWriter->startElement('c:x');
+ $objWriter->writeAttribute('val', $x);
+ $objWriter->endElement();
+ }
+
+ $y = $layout->getYPosition();
+ if (!is_null($y)) {
+ $objWriter->startElement('c:y');
+ $objWriter->writeAttribute('val', $y);
+ $objWriter->endElement();
+ }
+
+ $w = $layout->getWidth();
+ if (!is_null($w)) {
+ $objWriter->startElement('c:w');
+ $objWriter->writeAttribute('val', $w);
+ $objWriter->endElement();
+ }
+
+ $h = $layout->getHeight();
+ if (!is_null($h)) {
+ $objWriter->startElement('c:h');
+ $objWriter->writeAttribute('val', $h);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Alternate Content block
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeAlternateContent($objWriter)
+ {
+ $objWriter->startElement('mc:AlternateContent');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+
+ $objWriter->startElement('mc:Choice');
+ $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart');
+ $objWriter->writeAttribute('Requires', 'c14');
+
+ $objWriter->startElement('c14:style');
+ $objWriter->writeAttribute('val', '102');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('mc:Fallback');
+ $objWriter->startElement('c:style');
+ $objWriter->writeAttribute('val', '2');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Printer Settings
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writePrintSettings($objWriter)
+ {
+ $objWriter->startElement('c:printSettings');
+
+ $objWriter->startElement('c:headerFooter');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageMargins');
+ $objWriter->writeAttribute('footer', 0.3);
+ $objWriter->writeAttribute('header', 0.3);
+ $objWriter->writeAttribute('r', 0.7);
+ $objWriter->writeAttribute('l', 0.7);
+ $objWriter->writeAttribute('t', 0.75);
+ $objWriter->writeAttribute('b', 0.75);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageSetup');
+ $objWriter->writeAttribute('orientation', "portrait");
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Comments.php b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Comments.php
new file mode 100644
index 0000000..e7b9c99
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/Comments.php
@@ -0,0 +1,268 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0','UTF-8','yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // Authors cache
+ $authors = array();
+ $authorId = 0;
+ foreach ($comments as $comment) {
+ if (!isset($authors[$comment->getAuthor()])) {
+ $authors[$comment->getAuthor()] = $authorId++;
+ }
+ }
+
+ // comments
+ $objWriter->startElement('comments');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // Loop through authors
+ $objWriter->startElement('authors');
+ foreach ($authors as $author => $index) {
+ $objWriter->writeElement('author', $author);
+ }
+ $objWriter->endElement();
+
+ // Loop through comments
+ $objWriter->startElement('commentList');
+ foreach ($comments as $key => $value) {
+ $this->_writeComment($objWriter, $key, $value, $authors);
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write comment to XML format
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference
+ * @param PHPExcel_Comment $pComment Comment
+ * @param array $pAuthors Array of authors
+ * @throws PHPExcel_Writer_Exception
+ */
+ public function _writeComment(PHPExcel_Shared_XMLWriter $objWriter = null, $pCellReference = 'A1', PHPExcel_Comment $pComment = null, $pAuthors = null)
+ {
+ // comment
+ $objWriter->startElement('comment');
+ $objWriter->writeAttribute('ref', $pCellReference);
+ $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]);
+
+ // text
+ $objWriter->startElement('text');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write VML comments to XML format
+ *
+ * @param PHPExcel_Worksheet $pWorksheet
+ * @return string XML Output
+ * @throws PHPExcel_Writer_Exception
+ */
+ public function writeVMLComments(PHPExcel_Worksheet $pWorksheet = null)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0','UTF-8','yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t202');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '202');
+ $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through comments
+ foreach ($comments as $key => $value) {
+ $this->_writeVMLComment($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference
+ * @param PHPExcel_Comment $pComment Comment
+ * @throws PHPExcel_Writer_Exception
+ */
+ public function _writeVMLComment(PHPExcel_Shared_XMLWriter $objWriter = null, $pCellReference = 'A1', PHPExcel_Comment $pComment = null)
+ {
+ // Metadata
+ list($column, $row) = PHPExcel_Cell::coordinateFromString($pCellReference);
+ $column = PHPExcel_Cell::columnIndexFromString($column);
+ $id = 1024 + $column + $row;
+ $id = substr($id, 0, 4);
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t202');
+ $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden'));
+ $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->writeAttribute('o:insetmode', 'auto');
+
+ // v:fill
+ $objWriter->startElement('v:fill');
+ $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->endElement();
+
+ // v:shadow
+ $objWriter->startElement('v:shadow');
+ $objWriter->writeAttribute('on', 't');
+ $objWriter->writeAttribute('color', 'black');
+ $objWriter->writeAttribute('obscured', 't');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:connecttype', 'none');
+ $objWriter->endElement();
+
+ // v:textbox
+ $objWriter->startElement('v:textbox');
+ $objWriter->writeAttribute('style', 'mso-direction-alt:auto');
+
+ // div
+ $objWriter->startElement('div');
+ $objWriter->writeAttribute('style', 'text-align:left');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // x:ClientData
+ $objWriter->startElement('x:ClientData');
+ $objWriter->writeAttribute('ObjectType', 'Note');
+
+ // x:MoveWithCells
+ $objWriter->writeElement('x:MoveWithCells', '');
+
+ // x:SizeWithCells
+ $objWriter->writeElement('x:SizeWithCells', '');
+
+ // x:Anchor
+ //$objWriter->writeElement('x:Anchor', $column . ', 15, ' . ($row - 2) . ', 10, ' . ($column + 4) . ', 15, ' . ($row + 5) . ', 18');
+
+ // x:AutoFill
+ $objWriter->writeElement('x:AutoFill', 'False');
+
+ // x:Row
+ $objWriter->writeElement('x:Row', ($row - 1));
+
+ // x:Column
+ $objWriter->writeElement('x:Column', ($column - 1));
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/Excel2007/ContentTypes.php b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/ContentTypes.php
new file mode 100644
index 0000000..c50c73d
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/Excel2007/ContentTypes.php
@@ -0,0 +1,261 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0','UTF-8','yes');
+
+ // Types
+ $objWriter->startElement('Types');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types');
+
+ // Theme
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml'
+ );
+
+ // Styles
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'
+ );
+
+ // Rels
+ $this->_writeDefaultContentType(
+ $objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml'
+ );
+
+ // XML
+ $this->_writeDefaultContentType(
+ $objWriter, 'xml', 'application/xml'
+ );
+
+ // VML
+ $this->_writeDefaultContentType(
+ $objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing'
+ );
+
+ // Workbook
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'
+ );
+
+ // DocProps
+ $this->_writeOverrideContentType(
+ $objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml'
+ );
+
+ $this->_writeOverrideContentType(
+ $objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml'
+ );
+
+ $customPropertyList = $pPHPExcel->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ $this->_writeOverrideContentType(
+ $objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml'
+ );
+ }
+
+ // Worksheets
+ $sheetCount = $pPHPExcel->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'
+ );
+ }
+
+ // Shared strings
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'
+ );
+
+ // Add worksheet relationship content types
+ $chart = 1;
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $drawings = $pPHPExcel->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ $chartCount = ($includeCharts) ? $pPHPExcel->getSheet($i)->getChartCount() : 0;
+
+ // We need a drawing relationship for the worksheet if we have either drawings or charts
+ if (($drawingCount > 0) || ($chartCount > 0)) {
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml'
+ );
+ }
+
+ // If we have charts, then we need a chart relationship for every individual chart
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'
+ );
+ }
+ }
+ }
+
+ // Comments
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($pPHPExcel->getSheet($i)->getComments()) > 0) {
+ $this->_writeOverrideContentType(
+ $objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml'
+ );
+ }
+ }
+
+ // Add media content-types
+ $aMediaContentTypes = array();
+ $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count();
+ for ($i = 0; $i < $mediaCount; ++$i) {
+ $extension = '';
+ $mimeType = '';
+
+ if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof PHPExcel_Worksheet_Drawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension());
+ $mimeType = $this->_getImageMimeType( $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath() );
+ } else if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof PHPExcel_Worksheet_MemoryDrawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType();
+ }
+
+ if (!isset( $aMediaContentTypes[$extension]) ) {
+ $aMediaContentTypes[$extension] = $mimeType;
+
+ $this->_writeDefaultContentType(
+ $objWriter, $extension, $mimeType
+ );
+ }
+ }
+
+ $sheetCount = $pPHPExcel->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($pPHPExcel->getSheet()->getHeaderFooter()->getImages()) > 0) {
+ foreach ($pPHPExcel->getSheet()->getHeaderFooter()->getImages() as $image) {
+ if (!isset( $aMediaContentTypes[strtolower($image->getExtension())]) ) {
+ $aMediaContentTypes[strtolower($image->getExtension())] = $this->_getImageMimeType( $image->getPath() );
+
+ $this->_writeDefaultContentType(
+ $objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]
+ );
+ }
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Get image mime type
+ *
+ * @param string $pFile Filename
+ * @return string Mime Type
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _getImageMimeType($pFile = '')
+ {
+ if (PHPExcel_Shared_File::file_exists($pFile)) {
+ $image = getimagesize($pFile);
+ return image_type_to_mime_type($image[2]);
+ } else {
+ throw new PHPExcel_Writer_Exception("File $pFile does not exist");
+ }
+ }
+
+ /**
+ * Write Default content type
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeDefaultContentType(PHPExcel_Shared_XMLWriter $objWriter = null, $pPartname = '', $pContentType = '')
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Default');
+ $objWriter->writeAttribute('Extension', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new PHPExcel_Writer_Exception("Invalid parameters passed.");
+ }
+ }
+
+ /**
+ * Write Override content type
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function _writeOverrideContentType(PHPExcel_Shared_XMLWriter $objWriter = null, $pPartname = '', $pContentType = '')
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Override');
+ $objWriter->writeAttribute('PartName', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new PHPExcel_Writer_Exception("Invalid parameters passed.");
+ }
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Cell/Comment.php b/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Cell/Comment.php
new file mode 100644
index 0000000..f1e98a1
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Cell/Comment.php
@@ -0,0 +1,63 @@
+
+ */
+class PHPExcel_Writer_OpenDocument_Cell_Comment
+{
+ public static function write(PHPExcel_Shared_XMLWriter $objWriter, PHPExcel_Cell $cell)
+ {
+ $comments = $cell->getWorksheet()->getComments();
+ if (!isset($comments[$cell->getCoordinate()])) {
+ return;
+ }
+ $comment = $comments[$cell->getCoordinate()];
+
+ $objWriter->startElement('office:annotation');
+ //$objWriter->writeAttribute('draw:style-name', 'gr1');
+ //$objWriter->writeAttribute('draw:text-style-name', 'P1');
+ $objWriter->writeAttribute('svg:width', $comment->getWidth());
+ $objWriter->writeAttribute('svg:height', $comment->getHeight());
+ $objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
+ $objWriter->writeAttribute('svg:y', $comment->getMarginTop());
+ //$objWriter->writeAttribute('draw:caption-point-x', $comment->getMarginLeft());
+ //$objWriter->writeAttribute('draw:caption-point-y', $comment->getMarginTop());
+ $objWriter->writeElement('dc:creator', $comment->getAuthor());
+ // TODO: Not realized in PHPExcel_Comment yet.
+ //$objWriter->writeElement('dc:date', $comment->getDate());
+ $objWriter->writeElement('text:p', $comment->getText()->getPlainText());
+ //$objWriter->writeAttribute('draw:text-style-name', 'P1');
+ $objWriter->endElement();
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Content.php b/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Content.php
new file mode 100644
index 0000000..a34b167
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/OpenDocument/Content.php
@@ -0,0 +1,272 @@
+
+ */
+class PHPExcel_Writer_OpenDocument_Content extends PHPExcel_Writer_OpenDocument_WriterPart
+{
+ const NUMBER_COLS_REPEATED_MAX = 1024;
+ const NUMBER_ROWS_REPEATED_MAX = 1048576;
+
+ /**
+ * Write content.xml to XML format
+ *
+ * @param PHPExcel $pPHPExcel
+ * @return string XML Output
+ * @throws PHPExcel_Writer_Exception
+ */
+ public function write(PHPExcel $pPHPExcel = null)
+ {
+ if (!$pPHPExcel) {
+ $pPHPExcel = $this->getParentWriter()->getPHPExcel(); /* @var $pPHPExcel PHPExcel */
+ }
+
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Content
+ $objWriter->startElement('office:document-content');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
+ $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
+ $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:scripts');
+ $objWriter->writeElement('office:font-face-decls');
+ $objWriter->writeElement('office:automatic-styles');
+
+ $objWriter->startElement('office:body');
+ $objWriter->startElement('office:spreadsheet');
+ $objWriter->writeElement('table:calculation-settings');
+ $this->writeSheets($objWriter);
+ $objWriter->writeElement('table:named-expressions');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write sheets
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter
+ */
+ private function writeSheets(PHPExcel_Shared_XMLWriter $objWriter)
+ {
+ $pPHPExcel = $this->getParentWriter()->getPHPExcel(); /* @var $pPHPExcel PHPExcel */
+
+ $sheet_count = $pPHPExcel->getSheetCount();
+ for ($i = 0; $i < $sheet_count; $i++) {
+ //$this->getWriterPart('Worksheet')->writeWorksheet());
+ $objWriter->startElement('table:table');
+ $objWriter->writeAttribute('table:name', $pPHPExcel->getSheet($i)->getTitle());
+ $objWriter->writeElement('office:forms');
+ $objWriter->startElement('table:table-column');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $this->writeRows($objWriter, $pPHPExcel->getSheet($i));
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write rows of the specified sheet
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter
+ * @param PHPExcel_Worksheet $sheet
+ */
+ private function writeRows(PHPExcel_Shared_XMLWriter $objWriter, PHPExcel_Worksheet $sheet)
+ {
+ $number_rows_repeated = self::NUMBER_ROWS_REPEATED_MAX;
+ $span_row = 0;
+ $rows = $sheet->getRowIterator();
+ while ($rows->valid()) {
+ $number_rows_repeated--;
+ $row = $rows->current();
+ if ($row->getCellIterator()->valid()) {
+ if ($span_row) {
+ $objWriter->startElement('table:table-row');
+ if ($span_row > 1) {
+ $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
+ }
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $span_row = 0;
+ }
+ $objWriter->startElement('table:table-row');
+ $this->writeCells($objWriter, $row);
+ $objWriter->endElement();
+ } else {
+ $span_row++;
+ }
+ $rows->next();
+ }
+ }
+
+ /**
+ * Write cells of the specified row
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter
+ * @param PHPExcel_Worksheet_Row $row
+ * @throws PHPExcel_Writer_Exception
+ */
+ private function writeCells(PHPExcel_Shared_XMLWriter $objWriter, PHPExcel_Worksheet_Row $row)
+ {
+ $number_cols_repeated = self::NUMBER_COLS_REPEATED_MAX;
+ $prev_column = -1;
+ $cells = $row->getCellIterator();
+ while ($cells->valid()) {
+ $cell = $cells->current();
+ $column = PHPExcel_Cell::columnIndexFromString($cell->getColumn()) - 1;
+
+ $this->writeCellSpan($objWriter, $column, $prev_column);
+ $objWriter->startElement('table:table-cell');
+
+ switch ($cell->getDataType()) {
+ case PHPExcel_Cell_DataType::TYPE_BOOL:
+ $objWriter->writeAttribute('office:value-type', 'boolean');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+ break;
+
+ case PHPExcel_Cell_DataType::TYPE_ERROR:
+ throw new PHPExcel_Writer_Exception('Writing of error not implemented yet.');
+ break;
+
+ case PHPExcel_Cell_DataType::TYPE_FORMULA:
+ try {
+ $formula_value = $cell->getCalculatedValue();
+ } catch (Exception $e) {
+ $formula_value = $cell->getValue();
+ }
+ $objWriter->writeAttribute('table:formula', 'of:' . $cell->getValue());
+ if (is_numeric($formula_value)) {
+ $objWriter->writeAttribute('office:value-type', 'float');
+ } else {
+ $objWriter->writeAttribute('office:value-type', 'string');
+ }
+ $objWriter->writeAttribute('office:value', $formula_value);
+ $objWriter->writeElement('text:p', $formula_value);
+ break;
+
+ case PHPExcel_Cell_DataType::TYPE_INLINE:
+ throw new PHPExcel_Writer_Exception('Writing of inline not implemented yet.');
+ break;
+
+ case PHPExcel_Cell_DataType::TYPE_NUMERIC:
+ $objWriter->writeAttribute('office:value-type', 'float');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+ break;
+
+ case PHPExcel_Cell_DataType::TYPE_STRING:
+ $objWriter->writeAttribute('office:value-type', 'string');
+ $objWriter->writeElement('text:p', $cell->getValue());
+ break;
+ }
+ PHPExcel_Writer_OpenDocument_Cell_Comment::write($objWriter, $cell);
+ $objWriter->endElement();
+ $prev_column = $column;
+ $cells->next();
+ }
+ $number_cols_repeated = $number_cols_repeated - $prev_column - 1;
+ if ($number_cols_repeated > 0) {
+ if ($number_cols_repeated > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $number_cols_repeated);
+ $objWriter->endElement();
+ } else {
+ $objWriter->writeElement('table:table-cell');
+ }
+ }
+ }
+
+ /**
+ * Write span
+ *
+ * @param PHPExcel_Shared_XMLWriter $objWriter
+ * @param integer $curColumn
+ * @param integer $prevColumn
+ */
+ private function writeCellSpan(PHPExcel_Shared_XMLWriter $objWriter, $curColumn, $prevColumn)
+ {
+ $diff = $curColumn - $prevColumn - 1;
+ if (1 === $diff) {
+ $objWriter->writeElement('table:table-cell');
+ } elseif ($diff > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $diff);
+ $objWriter->endElement();
+ }
+ }
+}
diff --git a/framework/library/phpexcel/PHPExcel/Writer/PDF/Core.php b/framework/library/phpexcel/PHPExcel/Writer/PDF/Core.php
new file mode 100644
index 0000000..f058b59
--- /dev/null
+++ b/framework/library/phpexcel/PHPExcel/Writer/PDF/Core.php
@@ -0,0 +1,364 @@
+ 'LETTER', // (8.5 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER_SMALL
+ => 'LETTER', // (8.5 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_TABLOID
+ => array(792.00, 1224.00), // (11 in. by 17 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LEDGER
+ => array(1224.00, 792.00), // (17 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LEGAL
+ => 'LEGAL', // (8.5 in. by 14 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STATEMENT
+ => array(396.00, 612.00), // (5.5 in. by 8.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_EXECUTIVE
+ => 'EXECUTIVE', // (7.25 in. by 10.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3
+ => 'A3', // (297 mm by 420 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4
+ => 'A4', // (210 mm by 297 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4_SMALL
+ => 'A4', // (210 mm by 297 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5
+ => 'A5', // (148 mm by 210 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_B4
+ => 'B4', // (250 mm by 353 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_B5
+ => 'B5', // (176 mm by 250 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_FOLIO
+ => 'FOLIO', // (8.5 in. by 13 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_QUARTO
+ => array(609.45, 779.53), // (215 mm by 275 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STANDARD_1
+ => array(720.00, 1008.00), // (10 in. by 14 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STANDARD_2
+ => array(792.00, 1224.00), // (11 in. by 17 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NOTE
+ => 'LETTER', // (8.5 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NO9_ENVELOPE
+ => array(279.00, 639.00), // (3.875 in. by 8.875 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NO10_ENVELOPE
+ => array(297.00, 684.00), // (4.125 in. by 9.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NO11_ENVELOPE
+ => array(324.00, 747.00), // (4.5 in. by 10.375 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NO12_ENVELOPE
+ => array(342.00, 792.00), // (4.75 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_NO14_ENVELOPE
+ => array(360.00, 828.00), // (5 in. by 11.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C
+ => array(1224.00, 1584.00), // (17 in. by 22 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_D
+ => array(1584.00, 2448.00), // (22 in. by 34 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_E
+ => array(2448.00, 3168.00), // (34 in. by 44 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_DL_ENVELOPE
+ => array(311.81, 623.62), // (110 mm by 220 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C5_ENVELOPE
+ => 'C5', // (162 mm by 229 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C3_ENVELOPE
+ => 'C3', // (324 mm by 458 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C4_ENVELOPE
+ => 'C4', // (229 mm by 324 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C6_ENVELOPE
+ => 'C6', // (114 mm by 162 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_C65_ENVELOPE
+ => array(323.15, 649.13), // (114 mm by 229 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_B4_ENVELOPE
+ => 'B4', // (250 mm by 353 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_B5_ENVELOPE
+ => 'B5', // (176 mm by 250 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_B6_ENVELOPE
+ => array(498.90, 354.33), // (176 mm by 125 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_ITALY_ENVELOPE
+ => array(311.81, 651.97), // (110 mm by 230 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_MONARCH_ENVELOPE
+ => array(279.00, 540.00), // (3.875 in. by 7.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_6_3_4_ENVELOPE
+ => array(261.00, 468.00), // (3.625 in. by 6.5 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_US_STANDARD_FANFOLD
+ => array(1071.00, 792.00), // (14.875 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_GERMAN_STANDARD_FANFOLD
+ => array(612.00, 864.00), // (8.5 in. by 12 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_GERMAN_LEGAL_FANFOLD
+ => 'FOLIO', // (8.5 in. by 13 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_ISO_B4
+ => 'B4', // (250 mm by 353 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_JAPANESE_DOUBLE_POSTCARD
+ => array(566.93, 419.53), // (200 mm by 148 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STANDARD_PAPER_1
+ => array(648.00, 792.00), // (9 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STANDARD_PAPER_2
+ => array(720.00, 792.00), // (10 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_STANDARD_PAPER_3
+ => array(1080.00, 792.00), // (15 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_INVITE_ENVELOPE
+ => array(623.62, 623.62), // (220 mm by 220 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER_EXTRA_PAPER
+ => array(667.80, 864.00), // (9.275 in. by 12 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LEGAL_EXTRA_PAPER
+ => array(667.80, 1080.00), // (9.275 in. by 15 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_TABLOID_EXTRA_PAPER
+ => array(841.68, 1296.00), // (11.69 in. by 18 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4_EXTRA_PAPER
+ => array(668.98, 912.76), // (236 mm by 322 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER_TRANSVERSE_PAPER
+ => array(595.80, 792.00), // (8.275 in. by 11 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4_TRANSVERSE_PAPER
+ => 'A4', // (210 mm by 297 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER
+ => array(667.80, 864.00), // (9.275 in. by 12 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_SUPERA_SUPERA_A4_PAPER
+ => array(643.46, 1009.13), // (227 mm by 356 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_SUPERB_SUPERB_A3_PAPER
+ => array(864.57, 1380.47), // (305 mm by 487 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER_PLUS_PAPER
+ => array(612.00, 913.68), // (8.5 in. by 12.69 in.)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4_PLUS_PAPER
+ => array(595.28, 935.43), // (210 mm by 330 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5_TRANSVERSE_PAPER
+ => 'A5', // (148 mm by 210 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_JIS_B5_TRANSVERSE_PAPER
+ => array(515.91, 728.50), // (182 mm by 257 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3_EXTRA_PAPER
+ => array(912.76, 1261.42), // (322 mm by 445 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5_EXTRA_PAPER
+ => array(493.23, 666.14), // (174 mm by 235 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_ISO_B5_EXTRA_PAPER
+ => array(569.76, 782.36), // (201 mm by 276 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A2_PAPER
+ => 'A2', // (420 mm by 594 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3_TRANSVERSE_PAPER
+ => 'A3', // (297 mm by 420 mm)
+ PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER
+ => array(912.76, 1261.42) // (322 mm by 445 mm)
+ );
+
+ /**
+ * Create a new PHPExcel_Writer_PDF
+ *
+ * @param PHPExcel $phpExcel PHPExcel object
+ */
+ public function __construct(PHPExcel $phpExcel)
+ {
+ parent::__construct($phpExcel);
+ $this->setUseInlineCss(TRUE);
+ $this->_tempDir = PHPExcel_Shared_File::sys_get_temp_dir();
+ }
+
+ /**
+ * Get Font
+ *
+ * @return string
+ */
+ public function getFont()
+ {
+ return $this->_font;
+ }
+
+ /**
+ * Set font. Examples:
+ * 'arialunicid0-chinese-simplified'
+ * 'arialunicid0-chinese-traditional'
+ * 'arialunicid0-korean'
+ * 'arialunicid0-japanese'
+ *
+ * @param string $fontName
+ */
+ public function setFont($fontName)
+ {
+ $this->_font = $fontName;
+ return $this;
+ }
+
+ /**
+ * Get Paper Size
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->_paperSize;
+ }
+
+ /**
+ * Set Paper Size
+ *
+ * @param string $pValue Paper size
+ * @return PHPExcel_Writer_PDF
+ */
+ public function setPaperSize($pValue = PHPExcel_Worksheet_PageSetup::PAPERSIZE_LETTER)
+ {
+ $this->_paperSize = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get Orientation
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->_orientation;
+ }
+
+ /**
+ * Set Orientation
+ *
+ * @param string $pValue Page orientation
+ * @return PHPExcel_Writer_PDF
+ */
+ public function setOrientation($pValue = PHPExcel_Worksheet_PageSetup::ORIENTATION_DEFAULT)
+ {
+ $this->_orientation = $pValue;
+ return $this;
+ }
+
+ /**
+ * Get temporary storage directory
+ *
+ * @return string
+ */
+ public function getTempDir()
+ {
+ return $this->_tempDir;
+ }
+
+ /**
+ * Set temporary storage directory
+ *
+ * @param string $pValue Temporary storage directory
+ * @throws PHPExcel_Writer_Exception when directory does not exist
+ * @return PHPExcel_Writer_PDF
+ */
+ public function setTempDir($pValue = '')
+ {
+ if (is_dir($pValue)) {
+ $this->_tempDir = $pValue;
+ } else {
+ throw new PHPExcel_Writer_Exception("Directory does not exist: $pValue");
+ }
+ return $this;
+ }
+
+ /**
+ * Save PHPExcel to PDF file, pre-save
+ *
+ * @param string $pFilename Name of the file to save as
+ * @throws PHPExcel_Writer_Exception
+ */
+ protected function prepareForSave($pFilename = NULL)
+ {
+ // garbage collect
+ $this->_phpExcel->garbageCollect();
+
+ $this->_saveArrayReturnType = PHPExcel_Calculation::getArrayReturnType();
+ PHPExcel_Calculation::setArrayReturnType(PHPExcel_Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Open file
+ $fileHandle = fopen($pFilename, 'w');
+ if ($fileHandle === FALSE) {
+ throw new PHPExcel_Writer_Exception("Could not open file $pFilename for writing.");
+ }
+
+ // Set PDF
+ $this->_isPdf = TRUE;
+ // Build CSS
+ $this->buildCSS(TRUE);
+
+ return $fileHandle;
+ }
+
+ /**
+ * Save PHPExcel to PDF file, post-save
+ *
+ * @param resource $fileHandle
+ * @throws PHPExcel_Writer_Exception
+ */
+ protected function restoreStateAfterSave($fileHandle)
+ {
+ // Close file
+ fclose($fileHandle);
+
+ PHPExcel_Calculation::setArrayReturnType($this->_saveArrayReturnType);
+ }
+
+}
diff --git a/web/cityagent.php b/web/cityagent.php
new file mode 100644
index 0000000..7e26948
--- /dev/null
+++ b/web/cityagent.php
@@ -0,0 +1,23 @@
+model('attachment');
+
+$_W['catalog'] = 'web';
+$_W['plugin'] = $plugin = !empty($_GPC['p']) ? $_GPC['p'] : 'dashboard';
+$_W['controller'] = $controller = !empty($_GPC['ac']) ? $_GPC['ac'] : 'dashboard';
+$_W['method'] = $method = !empty($_GPC['do']) ? $_GPC['do'] : 'index';
+Func_loader::web('cover');
+$_W['wlsetting'] = Setting::wlsetting_load();
+$_W['attachurl'] = attachment_set_attach_url();
+
+wl_new_method($plugin, $controller, $method, $_W['catalog']);
\ No newline at end of file
diff --git a/web/citystore.php b/web/citystore.php
new file mode 100644
index 0000000..3a1e742
--- /dev/null
+++ b/web/citystore.php
@@ -0,0 +1,23 @@
+model('attachment');
+
+$_W['catalog'] = 'web';
+$_W['plugin'] = $plugin = !empty($_GPC['p']) ? $_GPC['p'] : 'dashboard';
+$_W['controller'] = $controller = !empty($_GPC['ac']) ? $_GPC['ac'] : 'dashboard';
+$_W['method'] = $method = !empty($_GPC['do']) ? $_GPC['do'] : 'index';
+Func_loader::web('storecover');
+$_W['wlsetting'] = Setting::wlsetting_load();
+$_W['attachurl'] = attachment_set_attach_url();
+
+wl_new_method($plugin, $controller, $method, $_W['catalog']);
\ No newline at end of file
diff --git a/web/citysys.php b/web/citysys.php
new file mode 100644
index 0000000..e74094e
--- /dev/null
+++ b/web/citysys.php
@@ -0,0 +1,24 @@
+model('attachment');
+
+$_W['token'] = token();
+$_W['catalog'] = 'sys';
+$_W['plugin'] = $plugin = !empty($_GPC['p']) ? $_GPC['p'] : 'dashboard';
+$_W['controller'] = $controller = !empty($_GPC['ac']) ? $_GPC['ac'] : 'dashboard';
+$_W['method'] = $method = !empty($_GPC['do']) ? $_GPC['do'] : 'index';
+Func_loader::web('syscover');
+$_W['wlsetting'] = Setting::wlsetting_load();
+$_W['attachurl'] = attachment_set_attach_url();
+
+wl_new_method($plugin, $controller, $method, $_W['catalog']);
\ No newline at end of file
diff --git a/web/common/common.func.php b/web/common/common.func.php
new file mode 100644
index 0000000..c9d74b0
--- /dev/null
+++ b/web/common/common.func.php
@@ -0,0 +1,323 @@
+model('module');
+load()->model('miniapp');
+
+function current_operate_is_controller()
+{
+ global $_W, $_GPC;
+ $result = 0;
+ if (!$_W['isfounder']) {
+ return $result;
+ }
+ $result = igetcookie('__iscontroller');
+ if (isset($_GPC['iscontroller'])) {
+ if (1 == $_GPC['iscontroller']) {
+ $result = 1;
+ isetcookie('__iscontroller', $result);
+ return $result;
+ }
+ if (0 == $_GPC['iscontroller']) {
+ $result = 0;
+ }
+ }
+
+ if (in_array(FRAME, array('welcome', 'module_manage', 'user_manage', 'permission', 'system', 'site'))) {
+ $result = 1;
+ }
+ if (in_array(FRAME, array('account', 'wxapp')) && (($_GPC['m'] || $_GPC['module_name']) != 'store')) {
+ $result = 0;
+ }
+ isetcookie('__iscontroller', $result);
+ return $result;
+}
+
+function system_modules()
+{
+ return module_system();
+}
+
+
+function url($segment, $params = array(), $contain_domain = false)
+{
+ return wurl($segment, $params, $contain_domain);
+}
+
+
+function message($msg, $redirect = '', $type = '', $tips = false, $extend = array())
+{
+ global $_W, $_GPC;
+
+ if ('refresh' == $redirect) {
+ $redirect = $_W['script_name'] . '?' . $_SERVER['QUERY_STRING'];
+ }
+ if ('referer' == $redirect) {
+ $redirect = referer();
+ }
+ $redirect = safe_gpc_url($redirect);
+
+ if ('' == $redirect) {
+ $type = in_array($type, array('success', 'error', 'info', 'warning', 'ajax', 'sql', 'expired')) ? $type : 'info';
+ } else {
+ $type = in_array($type, array('success', 'error', 'info', 'warning', 'ajax', 'sql', 'expired')) ? $type : 'success';
+ }
+ if ($_W['isajax'] || !empty($_GET['isajax']) || 'ajax' == $type) {
+ if ('ajax' != $type && !empty($_GPC['target'])) {
+ exit('
+");
+ } else {
+ $vars = array();
+ $vars['message'] = $msg;
+ $vars['redirect'] = $redirect;
+ $vars['type'] = $type;
+ exit(json_encode($vars));
+ }
+ }
+ if (empty($msg) && !empty($redirect)) {
+ header('Location: ' . $redirect);
+ exit;
+ }
+ $label = $type;
+ if ('error' == $type || 'expired' == $type) {
+ $label = 'danger';
+ }
+ if ('ajax' == $type || 'sql' == $type) {
+ $label = 'warning';
+ }
+
+ if ($tips) {
+ if (is_array($msg)) {
+ $message_cookie['title'] = 'MYSQL 错误';
+ $message_cookie['msg'] = 'php echo cutstr(' . $msg['sql'] . ', 300, 1);';
+ } else {
+ $message_cookie['title'] = $caption;
+ $message_cookie['msg'] = $msg;
+ }
+ $message_cookie['type'] = $label;
+ $message_cookie['redirect'] = $redirect ? $redirect : referer();
+ $message_cookie['msg'] = rawurlencode($message_cookie['msg']);
+ $extend_button = array();
+ if (!empty($extend) && is_array($extend)) {
+ foreach ($extend as $button) {
+ if (!empty($button['title']) && !empty($button['url'])) {
+ $button['url'] = safe_gpc_url($button['url'], false);
+ $button['title'] = rawurlencode($button['title']);
+ $extend_button[] = $button;
+ }
+ }
+ }
+ $message_cookie['extend'] = !empty($extend_button) ? $extend_button : '';
+
+ isetcookie('message', stripslashes(json_encode($message_cookie, JSON_UNESCAPED_UNICODE)));
+ header('Location: ' . $message_cookie['redirect']);
+ } else {
+ include template('common/message', TEMPLATE_INCLUDEPATH);
+ }
+ exit;
+}
+
+function iajax($code = 0, $message = '', $redirect = '')
+{
+ message(error($code, $message), $redirect, 'ajax', false);
+}
+
+function itoast($message, $redirect = '', $type = '', $extend = array())
+{
+ message($message, $redirect, $type, true, $extend);
+}
+
+
+function checklogin($url = '')
+{
+ global $_W;
+ if (empty($_W['uid'])) {
+ $url = safe_gpc_url($url);
+ if (!empty($_W['setting']['copyright']['showhomepage'])) {
+ itoast('', url('account/welcome'), 'warning');
+ } else {
+ itoast('', url('user/login', $url ? array('referer' => urlencode($url)) : ''), 'warning');
+ }
+ }
+ $cookie = json_decode(authcode(igetcookie('__session'), 'DECODE'), true);
+ if (empty($cookie['rember'])) {
+ $session = authcode(json_encode($cookie), 'encode');
+ $autosignout = (int)$_W['setting']['copyright']['autosignout'] > 0 ? (int)$_W['setting']['copyright']['autosignout'] * 60 : 0;
+ isetcookie('__session', $session, $autosignout, true);
+ }
+
+ return true;
+}
+
+function get_position_by_ip($ip = '')
+{
+ global $_W;
+ $ip = $ip ? $ip : $_W['clientip'];
+ $url = 'http://ip.taobao.com/outGetIpInfo?ip=' . $ip . '&accessKey=alibaba-inc';
+ $ip_content = file_get_contents($url);
+ $ip_content = json_decode($ip_content, true);
+ if (empty($ip_content) || $ip_content['code'] != 0) {
+ $res = @file_get_contents('https://whois.pconline.com.cn/ipJson.jsp');
+ $res = strtoutf8($res);
+ $json_matches = array();
+ preg_match('/{IPCallBack\((.+?)\);\}/', $res, $json_matches);
+ if (empty($json_matches[1])) {
+ return error(-1, '获取地址失败,请重新配置Ip查询接口');
+ }
+ $ip_content = array(
+ 'code' => 0,
+ 'data' => json_decode($json_matches[1], true)
+ );
+ }
+ return $ip_content;
+}
+
+function buildframes($framename = '')
+{
+ global $_W, $_GPC;
+ $frames = system_menu_permission_list();
+ $frames = frames_top_menu($frames);
+
+ return !empty($framename) ? ('system_welcome' == $framename ? $frames['account'] : $frames[$framename]) : $frames;
+}
+
+function frames_top_menu($frames)
+{
+ global $_W, $top_nav;
+ if (empty($frames)) {
+ return array();
+ }
+
+ foreach ($frames as $menuid => $menu) {
+// if ((!empty($menu['founder']) || in_array($menuid, array('module_manage', 'site', 'advertisement', 'appmarket'))) && !$_W['isadmin'] ||
+// ACCOUNT_MANAGE_NAME_CLERK == $_W['highest_role'] && in_array($menuid, array('account', 'wxapp', 'system', 'platform', 'welcome', 'account_manage')) && !$_W['isadmin'] && in_array($menuid, array('user_manage', 'permission')) ||
+// 'myself' == $menuid && $_W['isadmin'] ||
+// !$menu['is_display']) {
+// continue;
+// }
+
+ $top_nav[] = array(
+ 'title' => $menu['title'],
+ 'name' => $menuid,
+ 'url' => $menu['url'],
+ 'blank' => $menu['blank'],
+ 'icon' => $menu['icon'],
+ 'is_display' => $menu['is_display'],
+ 'is_system' => $menu['is_system'],
+ );
+ }
+ return $frames;
+}
+
+
+function filter_url($params)
+{
+ global $_W;
+ if (empty($params)) {
+ return '';
+ }
+ $query_arr = array();
+ $parse = parse_url($_W['siteurl']);
+ if (!empty($parse['query'])) {
+ $query = $parse['query'];
+ parse_str($query, $query_arr);
+ }
+ $params = explode(',', $params);
+ foreach ($params as $val) {
+ if (!empty($val)) {
+ $data = explode(':', $val);
+ $query_arr[$data[0]] = trim($data[1]);
+ }
+ }
+ $query_arr['page'] = 1;
+ $query = http_build_query($query_arr);
+
+ return './index.php?' . $query;
+}
+
+function url_params($url)
+{
+ $result = array();
+ if (empty($url)) {
+ return $result;
+ }
+ $components = parse_url($url);
+ $params = explode('&', $components['query']);
+ foreach ($params as $param) {
+ if (!empty($param)) {
+ $param_array = explode('=', $param);
+ $result[$param_array[0]] = $param_array[1];
+ }
+ }
+
+ return $result;
+}
+
+function frames_menu_append()
+{
+ $system_menu_default_permission = array(
+ 'founder' => array(),
+ 'vice_founder' => array(
+ 'system_setting_updatecache',
+ ),
+ 'owner' => array(
+ 'system_setting_updatecache',
+ ),
+ 'manager' => array(
+ 'system_setting_updatecache',
+ ),
+ 'operator' => array(
+ 'system_setting_updatecache',
+ ),
+ 'clerk' => array(),
+ 'expired' => array(
+ 'system_setting_updatecache',
+ ),
+ );
+
+ return $system_menu_default_permission;
+}
+
+
+function site_profile_perfect_tips()
+{
+ global $_W;
+
+ if ($_W['isfounder'] && (empty($_W['setting']['site']) || empty($_W['setting']['site']['profile_perfect']))) {
+ if (!defined('SITE_PROFILE_PERFECT_TIPS')) {
+ $url = url('cloud/profile');
+
+ return <<'+
+ ''+
+ ''+
+ ''+
+ ''+
+ '请尽快完善您在微擎云服务平台的站点注册信息。'+
+ ''+
+ ''+
+ '';
+ $('body').prepend(html);
+});
+EOF;
+ define('SITE_PROFILE_PERFECT_TIPS', true);
+ }
+ }
+
+ return '';
+}
+
+function strtoutf8($str)
+{
+ $current_encode = mb_detect_encoding($str, array('ASCII', 'GB2312', 'GBK', 'BIG5', 'UTF-8'));
+ return mb_convert_encoding($str, 'UTF-8', $current_encode);
+}
\ No newline at end of file
diff --git a/web/resource/components/area/cascade.js b/web/resource/components/area/cascade.js
new file mode 100644
index 0000000..8a80abb
--- /dev/null
+++ b/web/resource/components/area/cascade.js
@@ -0,0 +1,166 @@
+/**
+ * @name jQuery Cascdejs plugin
+ * @author zdy
+ * @version 1.0
+ */
+
+//首先需要初始化
+var xmlDoc;
+var TopnodeList;
+var citys;
+var countyNodes;
+var nodeindex = 0;
+var childnodeindex = 0;
+//获取xml文件
+function cascdeInit(v1,v2,v3) {
+ //打开xlmdocm文档
+ xmlDoc = loadXmlFile('./resource/components/area/Area.xml');
+ var dropElement1 = document.getElementById("sel-provance");
+ var dropElement2 = document.getElementById("sel-city");
+ var dropElement3 = document.getElementById("sel-area");
+ RemoveDropDownList(dropElement1);
+ RemoveDropDownList(dropElement2);
+ RemoveDropDownList(dropElement3);
+ if (window.ActiveXObject) {
+ TopnodeList = xmlDoc.selectSingleNode("address").childNodes;
+ }
+ else {
+ TopnodeList = xmlDoc.childNodes[0].getElementsByTagName("province");
+ }
+ if (TopnodeList.length > 0) {
+ //省份列表
+ var county;
+ var province;
+ var city;
+ for (var i = 0; i < TopnodeList.length; i++) {
+ //添加列表项目
+ county = TopnodeList[i];
+ var option = document.createElement("option");
+ option.value = county.getAttribute("name");
+ option.text = county.getAttribute("name");
+ if (v1 == option.value) {
+ option.selected = true;
+ nodeindex = i;
+ }
+ dropElement1.add(option);
+ }
+ if (TopnodeList.length > 0) {
+ //城市列表
+ citys = TopnodeList[nodeindex].getElementsByTagName("city")
+ for (var i = 0; i < citys.length; i++) {
+ var id = dropElement1.options[nodeindex].value;
+ //默认为第一个省份的城市
+ province = TopnodeList[nodeindex].getElementsByTagName("city");
+ var option = document.createElement("option");
+ option.value = province[i] .getAttribute("name");
+ option.text = province[i].getAttribute("name");
+ if (v2 == option.value) {
+ option.selected = true;
+ childnodeindex = i;
+ }
+ dropElement2.add(option);
+ }
+ selectcounty(v3);
+ }
+ }
+}
+
+/*
+//依据省设置城市,县
+*/
+function selectCity() {
+ var dropElement1 = document.getElementById("sel-provance");
+ var name = dropElement1.options[dropElement1.selectedIndex].value;
+ countyNodes = TopnodeList[dropElement1.selectedIndex];
+ var province = document.getElementById("sel-city");
+ var city = document.getElementById("sel-area");
+ RemoveDropDownList(province);
+ RemoveDropDownList(city);
+ var citynodes;
+ var countycodes;
+ if (window.ActiveXObject) {
+ citynodes = xmlDoc.selectSingleNode('//address/province [@name="' + name + '"]').childNodes;
+ } else {
+ citynodes = countyNodes.getElementsByTagName("city")
+ }
+ if (window.ActiveXObject) {
+ countycodes = citynodes[0].childNodes;
+ } else {
+ countycodes = citynodes[0].getElementsByTagName("county")
+ }
+
+ if (citynodes.length > 0) {
+ //城市
+ for (var i = 0; i < citynodes.length; i++) {
+ var provinceNode = citynodes[i];
+ var option = document.createElement("option");
+ option.value = provinceNode.getAttribute("name");
+ option.text = provinceNode.getAttribute("name");
+ province.add(option);
+ }
+ if (countycodes.length > 0) {
+ //填充选择省份的第一个城市的县列表
+ for (var i = 0; i < countycodes.length; i++) {
+ var dropElement2 = document.getElementById("sel-city");
+ var dropElement3 = document.getElementById("sel-area");
+ //取当天省份下第一个城市列表
+
+ //alert(cityNode.childNodes.length);
+ var option = document.createElement("option");
+ option.value = countycodes[i].getAttribute("name");
+ option.text = countycodes[i].getAttribute("name");
+ dropElement3.add(option);
+ }
+ }
+ }
+}
+/*
+//设置县,区
+*/
+function selectcounty(v3) {
+ var dropElement1 = document.getElementById("sel-provance");
+ var dropElement2 = document.getElementById("sel-city");
+ var name = dropElement2.options[dropElement2.selectedIndex].value;
+ var city = document.getElementById("sel-area");
+ var countys = TopnodeList[dropElement1.selectedIndex].getElementsByTagName("city")[dropElement2.selectedIndex].getElementsByTagName("county")
+ RemoveDropDownList(city);
+ for (var i = 0; i < countys.length; i++) {
+ var countyNode = countys[i];
+ var option = document.createElement("option");
+ option.value = countyNode.getAttribute("name");
+ option.text = countyNode.getAttribute("name");
+ if(v3==option.value){
+ option.selected=true;
+ }
+ city.add(option);
+ }
+}
+function RemoveDropDownList(obj) {
+ if (obj) {
+ var len = obj.options.length;
+ if (len > 0) {
+ for (var i = len; i >= 0; i--) {
+ obj.remove(i);
+ }
+ }
+ }
+}
+/*
+//读取xml文件
+*/
+function loadXmlFile(xmlFile) {
+ var xmlDom = null;
+ if (window.ActiveXObject) {
+ xmlDom = new ActiveXObject("Microsoft.XMLDOM");
+ xmlDom.async = false;
+ xmlDom.load(xmlFile) || xmlDom.loadXML(xmlFile);//如果用的是XML字符串//如果用的是xml文件
+ } else if (document.implementation && document.implementation.createDocument) {
+ var xmlhttp = new window.XMLHttpRequest();
+ xmlhttp.open("GET", xmlFile, false);
+ xmlhttp.send(null);
+ xmlDom = xmlhttp.responseXML;
+ } else {
+ xmlDom = null;
+ }
+ return xmlDom;
+}
\ No newline at end of file
diff --git a/web/resource/components/chart/Chart.js b/web/resource/components/chart/Chart.js
new file mode 100644
index 0000000..4b392d8
--- /dev/null
+++ b/web/resource/components/chart/Chart.js
@@ -0,0 +1,10635 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 2.3.0
+ *
+ * Copyright 2016 Nick Downie
+ * Released under the MIT license
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
+ */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o lum2) {
+ return (lum1 + 0.05) / (lum2 + 0.05);
+ }
+ return (lum2 + 0.05) / (lum1 + 0.05);
+ },
+
+ level: function (color2) {
+ var contrastRatio = this.contrast(color2);
+ if (contrastRatio >= 7.1) {
+ return 'AAA';
+ }
+
+ return (contrastRatio >= 4.5) ? 'AA' : '';
+ },
+
+ dark: function () {
+ // YIQ equation from http://24ways.org/2010/calculating-color-contrast
+ var rgb = this.values.rgb;
+ var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
+ return yiq < 128;
+ },
+
+ light: function () {
+ return !this.dark();
+ },
+
+ negate: function () {
+ var rgb = [];
+ for (var i = 0; i < 3; i++) {
+ rgb[i] = 255 - this.values.rgb[i];
+ }
+ this.setValues('rgb', rgb);
+ return this;
+ },
+
+ lighten: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[2] += hsl[2] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ darken: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[2] -= hsl[2] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ saturate: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[1] += hsl[1] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ desaturate: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[1] -= hsl[1] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ whiten: function (ratio) {
+ var hwb = this.values.hwb;
+ hwb[1] += hwb[1] * ratio;
+ this.setValues('hwb', hwb);
+ return this;
+ },
+
+ blacken: function (ratio) {
+ var hwb = this.values.hwb;
+ hwb[2] += hwb[2] * ratio;
+ this.setValues('hwb', hwb);
+ return this;
+ },
+
+ greyscale: function () {
+ var rgb = this.values.rgb;
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
+ var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
+ this.setValues('rgb', [val, val, val]);
+ return this;
+ },
+
+ clearer: function (ratio) {
+ var alpha = this.values.alpha;
+ this.setValues('alpha', alpha - (alpha * ratio));
+ return this;
+ },
+
+ opaquer: function (ratio) {
+ var alpha = this.values.alpha;
+ this.setValues('alpha', alpha + (alpha * ratio));
+ return this;
+ },
+
+ rotate: function (degrees) {
+ var hsl = this.values.hsl;
+ var hue = (hsl[0] + degrees) % 360;
+ hsl[0] = hue < 0 ? 360 + hue : hue;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ /**
+ * Ported from sass implementation in C
+ * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
+ */
+ mix: function (mixinColor, weight) {
+ var color1 = this;
+ var color2 = mixinColor;
+ var p = weight === undefined ? 0.5 : weight;
+
+ var w = 2 * p - 1;
+ var a = color1.alpha() - color2.alpha();
+
+ var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
+ var w2 = 1 - w1;
+
+ return this
+ .rgb(
+ w1 * color1.red() + w2 * color2.red(),
+ w1 * color1.green() + w2 * color2.green(),
+ w1 * color1.blue() + w2 * color2.blue()
+ )
+ .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
+ },
+
+ toJSON: function () {
+ return this.rgb();
+ },
+
+ clone: function () {
+ // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
+ // making the final build way to big to embed in Chart.js. So let's do it manually,
+ // assuming that values to clone are 1 dimension arrays containing only numbers,
+ // except 'alpha' which is a number.
+ var result = new Color();
+ var source = this.values;
+ var target = result.values;
+ var value, type;
+
+ for (var prop in source) {
+ if (source.hasOwnProperty(prop)) {
+ value = source[prop];
+ type = ({}).toString.call(value);
+ if (type === '[object Array]') {
+ target[prop] = value.slice(0);
+ } else if (type === '[object Number]') {
+ target[prop] = value;
+ } else {
+ console.error('unexpected color value:', value);
+ }
+ }
+ }
+
+ return result;
+ }
+};
+
+Color.prototype.spaces = {
+ rgb: ['red', 'green', 'blue'],
+ hsl: ['hue', 'saturation', 'lightness'],
+ hsv: ['hue', 'saturation', 'value'],
+ hwb: ['hue', 'whiteness', 'blackness'],
+ cmyk: ['cyan', 'magenta', 'yellow', 'black']
+};
+
+Color.prototype.maxes = {
+ rgb: [255, 255, 255],
+ hsl: [360, 100, 100],
+ hsv: [360, 100, 100],
+ hwb: [360, 100, 100],
+ cmyk: [100, 100, 100, 100]
+};
+
+Color.prototype.getValues = function (space) {
+ var values = this.values;
+ var vals = {};
+
+ for (var i = 0; i < space.length; i++) {
+ vals[space.charAt(i)] = values[space][i];
+ }
+
+ if (values.alpha !== 1) {
+ vals.a = values.alpha;
+ }
+
+ // {r: 255, g: 255, b: 255, a: 0.4}
+ return vals;
+};
+
+Color.prototype.setValues = function (space, vals) {
+ var values = this.values;
+ var spaces = this.spaces;
+ var maxes = this.maxes;
+ var alpha = 1;
+ var i;
+
+ if (space === 'alpha') {
+ alpha = vals;
+ } else if (vals.length) {
+ // [10, 10, 10]
+ values[space] = vals.slice(0, space.length);
+ alpha = vals[space.length];
+ } else if (vals[space.charAt(0)] !== undefined) {
+ // {r: 10, g: 10, b: 10}
+ for (i = 0; i < space.length; i++) {
+ values[space][i] = vals[space.charAt(i)];
+ }
+
+ alpha = vals.a;
+ } else if (vals[spaces[space][0]] !== undefined) {
+ // {red: 10, green: 10, blue: 10}
+ var chans = spaces[space];
+
+ for (i = 0; i < space.length; i++) {
+ values[space][i] = vals[chans[i]];
+ }
+
+ alpha = vals.alpha;
+ }
+
+ values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
+
+ if (space === 'alpha') {
+ return false;
+ }
+
+ var capped;
+
+ // cap values of the space prior converting all values
+ for (i = 0; i < space.length; i++) {
+ capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
+ values[space][i] = Math.round(capped);
+ }
+
+ // convert to all the other color spaces
+ for (var sname in spaces) {
+ if (sname !== space) {
+ values[sname] = convert[space][sname](values[space]);
+ }
+ }
+
+ return true;
+};
+
+Color.prototype.setSpace = function (space, args) {
+ var vals = args[0];
+
+ if (vals === undefined) {
+ // color.rgb()
+ return this.getValues(space);
+ }
+
+ // color.rgb(10, 10, 10)
+ if (typeof vals === 'number') {
+ vals = Array.prototype.slice.call(args);
+ }
+
+ this.setValues(space, vals);
+ return this;
+};
+
+Color.prototype.setChannel = function (space, index, val) {
+ var svalues = this.values[space];
+ if (val === undefined) {
+ // color.red()
+ return svalues[index];
+ } else if (val === svalues[index]) {
+ // color.red(color.red())
+ return this;
+ }
+
+ // color.red(100)
+ svalues[index] = val;
+ this.setValues(space, svalues);
+
+ return this;
+};
+
+if (typeof window !== 'undefined') {
+ window.Color = Color;
+}
+
+module.exports = Color;
+
+},{"2":2,"5":5}],4:[function(require,module,exports){
+/* MIT license */
+
+module.exports = {
+ rgb2hsl: rgb2hsl,
+ rgb2hsv: rgb2hsv,
+ rgb2hwb: rgb2hwb,
+ rgb2cmyk: rgb2cmyk,
+ rgb2keyword: rgb2keyword,
+ rgb2xyz: rgb2xyz,
+ rgb2lab: rgb2lab,
+ rgb2lch: rgb2lch,
+
+ hsl2rgb: hsl2rgb,
+ hsl2hsv: hsl2hsv,
+ hsl2hwb: hsl2hwb,
+ hsl2cmyk: hsl2cmyk,
+ hsl2keyword: hsl2keyword,
+
+ hsv2rgb: hsv2rgb,
+ hsv2hsl: hsv2hsl,
+ hsv2hwb: hsv2hwb,
+ hsv2cmyk: hsv2cmyk,
+ hsv2keyword: hsv2keyword,
+
+ hwb2rgb: hwb2rgb,
+ hwb2hsl: hwb2hsl,
+ hwb2hsv: hwb2hsv,
+ hwb2cmyk: hwb2cmyk,
+ hwb2keyword: hwb2keyword,
+
+ cmyk2rgb: cmyk2rgb,
+ cmyk2hsl: cmyk2hsl,
+ cmyk2hsv: cmyk2hsv,
+ cmyk2hwb: cmyk2hwb,
+ cmyk2keyword: cmyk2keyword,
+
+ keyword2rgb: keyword2rgb,
+ keyword2hsl: keyword2hsl,
+ keyword2hsv: keyword2hsv,
+ keyword2hwb: keyword2hwb,
+ keyword2cmyk: keyword2cmyk,
+ keyword2lab: keyword2lab,
+ keyword2xyz: keyword2xyz,
+
+ xyz2rgb: xyz2rgb,
+ xyz2lab: xyz2lab,
+ xyz2lch: xyz2lch,
+
+ lab2xyz: lab2xyz,
+ lab2rgb: lab2rgb,
+ lab2lch: lab2lch,
+
+ lch2lab: lch2lab,
+ lch2xyz: lch2xyz,
+ lch2rgb: lch2rgb
+}
+
+
+function rgb2hsl(rgb) {
+ var r = rgb[0]/255,
+ g = rgb[1]/255,
+ b = rgb[2]/255,
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ delta = max - min,
+ h, s, l;
+
+ if (max == min)
+ h = 0;
+ else if (r == max)
+ h = (g - b) / delta;
+ else if (g == max)
+ h = 2 + (b - r) / delta;
+ else if (b == max)
+ h = 4 + (r - g)/ delta;
+
+ h = Math.min(h * 60, 360);
+
+ if (h < 0)
+ h += 360;
+
+ l = (min + max) / 2;
+
+ if (max == min)
+ s = 0;
+ else if (l <= 0.5)
+ s = delta / (max + min);
+ else
+ s = delta / (2 - max - min);
+
+ return [h, s * 100, l * 100];
+}
+
+function rgb2hsv(rgb) {
+ var r = rgb[0],
+ g = rgb[1],
+ b = rgb[2],
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ delta = max - min,
+ h, s, v;
+
+ if (max == 0)
+ s = 0;
+ else
+ s = (delta/max * 1000)/10;
+
+ if (max == min)
+ h = 0;
+ else if (r == max)
+ h = (g - b) / delta;
+ else if (g == max)
+ h = 2 + (b - r) / delta;
+ else if (b == max)
+ h = 4 + (r - g) / delta;
+
+ h = Math.min(h * 60, 360);
+
+ if (h < 0)
+ h += 360;
+
+ v = ((max / 255) * 1000) / 10;
+
+ return [h, s, v];
+}
+
+function rgb2hwb(rgb) {
+ var r = rgb[0],
+ g = rgb[1],
+ b = rgb[2],
+ h = rgb2hsl(rgb)[0],
+ w = 1/255 * Math.min(r, Math.min(g, b)),
+ b = 1 - 1/255 * Math.max(r, Math.max(g, b));
+
+ return [h, w * 100, b * 100];
+}
+
+function rgb2cmyk(rgb) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255,
+ c, m, y, k;
+
+ k = Math.min(1 - r, 1 - g, 1 - b);
+ c = (1 - r - k) / (1 - k) || 0;
+ m = (1 - g - k) / (1 - k) || 0;
+ y = (1 - b - k) / (1 - k) || 0;
+ return [c * 100, m * 100, y * 100, k * 100];
+}
+
+function rgb2keyword(rgb) {
+ return reverseKeywords[JSON.stringify(rgb)];
+}
+
+function rgb2xyz(rgb) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255;
+
+ // assume sRGB
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+ var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
+ var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
+ var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
+
+ return [x * 100, y *100, z * 100];
+}
+
+function rgb2lab(rgb) {
+ var xyz = rgb2xyz(rgb),
+ x = xyz[0],
+ y = xyz[1],
+ z = xyz[2],
+ l, a, b;
+
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
+
+ return [l, a, b];
+}
+
+function rgb2lch(args) {
+ return lab2lch(rgb2lab(args));
+}
+
+function hsl2rgb(hsl) {
+ var h = hsl[0] / 360,
+ s = hsl[1] / 100,
+ l = hsl[2] / 100,
+ t1, t2, t3, rgb, val;
+
+ if (s == 0) {
+ val = l * 255;
+ return [val, val, val];
+ }
+
+ if (l < 0.5)
+ t2 = l * (1 + s);
+ else
+ t2 = l + s - l * s;
+ t1 = 2 * l - t2;
+
+ rgb = [0, 0, 0];
+ for (var i = 0; i < 3; i++) {
+ t3 = h + 1 / 3 * - (i - 1);
+ t3 < 0 && t3++;
+ t3 > 1 && t3--;
+
+ if (6 * t3 < 1)
+ val = t1 + (t2 - t1) * 6 * t3;
+ else if (2 * t3 < 1)
+ val = t2;
+ else if (3 * t3 < 2)
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
+ else
+ val = t1;
+
+ rgb[i] = val * 255;
+ }
+
+ return rgb;
+}
+
+function hsl2hsv(hsl) {
+ var h = hsl[0],
+ s = hsl[1] / 100,
+ l = hsl[2] / 100,
+ sv, v;
+
+ if(l === 0) {
+ // no need to do calc on black
+ // also avoids divide by 0 error
+ return [0, 0, 0];
+ }
+
+ l *= 2;
+ s *= (l <= 1) ? l : 2 - l;
+ v = (l + s) / 2;
+ sv = (2 * s) / (l + s);
+ return [h, sv * 100, v * 100];
+}
+
+function hsl2hwb(args) {
+ return rgb2hwb(hsl2rgb(args));
+}
+
+function hsl2cmyk(args) {
+ return rgb2cmyk(hsl2rgb(args));
+}
+
+function hsl2keyword(args) {
+ return rgb2keyword(hsl2rgb(args));
+}
+
+
+function hsv2rgb(hsv) {
+ var h = hsv[0] / 60,
+ s = hsv[1] / 100,
+ v = hsv[2] / 100,
+ hi = Math.floor(h) % 6;
+
+ var f = h - Math.floor(h),
+ p = 255 * v * (1 - s),
+ q = 255 * v * (1 - (s * f)),
+ t = 255 * v * (1 - (s * (1 - f))),
+ v = 255 * v;
+
+ switch(hi) {
+ case 0:
+ return [v, t, p];
+ case 1:
+ return [q, v, p];
+ case 2:
+ return [p, v, t];
+ case 3:
+ return [p, q, v];
+ case 4:
+ return [t, p, v];
+ case 5:
+ return [v, p, q];
+ }
+}
+
+function hsv2hsl(hsv) {
+ var h = hsv[0],
+ s = hsv[1] / 100,
+ v = hsv[2] / 100,
+ sl, l;
+
+ l = (2 - s) * v;
+ sl = s * v;
+ sl /= (l <= 1) ? l : 2 - l;
+ sl = sl || 0;
+ l /= 2;
+ return [h, sl * 100, l * 100];
+}
+
+function hsv2hwb(args) {
+ return rgb2hwb(hsv2rgb(args))
+}
+
+function hsv2cmyk(args) {
+ return rgb2cmyk(hsv2rgb(args));
+}
+
+function hsv2keyword(args) {
+ return rgb2keyword(hsv2rgb(args));
+}
+
+// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
+function hwb2rgb(hwb) {
+ var h = hwb[0] / 360,
+ wh = hwb[1] / 100,
+ bl = hwb[2] / 100,
+ ratio = wh + bl,
+ i, v, f, n;
+
+ // wh + bl cant be > 1
+ if (ratio > 1) {
+ wh /= ratio;
+ bl /= ratio;
+ }
+
+ i = Math.floor(6 * h);
+ v = 1 - bl;
+ f = 6 * h - i;
+ if ((i & 0x01) != 0) {
+ f = 1 - f;
+ }
+ n = wh + f * (v - wh); // linear interpolation
+
+ switch (i) {
+ default:
+ case 6:
+ case 0: r = v; g = n; b = wh; break;
+ case 1: r = n; g = v; b = wh; break;
+ case 2: r = wh; g = v; b = n; break;
+ case 3: r = wh; g = n; b = v; break;
+ case 4: r = n; g = wh; b = v; break;
+ case 5: r = v; g = wh; b = n; break;
+ }
+
+ return [r * 255, g * 255, b * 255];
+}
+
+function hwb2hsl(args) {
+ return rgb2hsl(hwb2rgb(args));
+}
+
+function hwb2hsv(args) {
+ return rgb2hsv(hwb2rgb(args));
+}
+
+function hwb2cmyk(args) {
+ return rgb2cmyk(hwb2rgb(args));
+}
+
+function hwb2keyword(args) {
+ return rgb2keyword(hwb2rgb(args));
+}
+
+function cmyk2rgb(cmyk) {
+ var c = cmyk[0] / 100,
+ m = cmyk[1] / 100,
+ y = cmyk[2] / 100,
+ k = cmyk[3] / 100,
+ r, g, b;
+
+ r = 1 - Math.min(1, c * (1 - k) + k);
+ g = 1 - Math.min(1, m * (1 - k) + k);
+ b = 1 - Math.min(1, y * (1 - k) + k);
+ return [r * 255, g * 255, b * 255];
+}
+
+function cmyk2hsl(args) {
+ return rgb2hsl(cmyk2rgb(args));
+}
+
+function cmyk2hsv(args) {
+ return rgb2hsv(cmyk2rgb(args));
+}
+
+function cmyk2hwb(args) {
+ return rgb2hwb(cmyk2rgb(args));
+}
+
+function cmyk2keyword(args) {
+ return rgb2keyword(cmyk2rgb(args));
+}
+
+
+function xyz2rgb(xyz) {
+ var x = xyz[0] / 100,
+ y = xyz[1] / 100,
+ z = xyz[2] / 100,
+ r, g, b;
+
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
+
+ // assume sRGB
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+ : r = (r * 12.92);
+
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+ : g = (g * 12.92);
+
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
+ : b = (b * 12.92);
+
+ r = Math.min(Math.max(0, r), 1);
+ g = Math.min(Math.max(0, g), 1);
+ b = Math.min(Math.max(0, b), 1);
+
+ return [r * 255, g * 255, b * 255];
+}
+
+function xyz2lab(xyz) {
+ var x = xyz[0],
+ y = xyz[1],
+ z = xyz[2],
+ l, a, b;
+
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
+
+ return [l, a, b];
+}
+
+function xyz2lch(args) {
+ return lab2lch(xyz2lab(args));
+}
+
+function lab2xyz(lab) {
+ var l = lab[0],
+ a = lab[1],
+ b = lab[2],
+ x, y, z, y2;
+
+ if (l <= 8) {
+ y = (l * 100) / 903.3;
+ y2 = (7.787 * (y / 100)) + (16 / 116);
+ } else {
+ y = 100 * Math.pow((l + 16) / 116, 3);
+ y2 = Math.pow(y / 100, 1/3);
+ }
+
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
+
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
+
+ return [x, y, z];
+}
+
+function lab2lch(lab) {
+ var l = lab[0],
+ a = lab[1],
+ b = lab[2],
+ hr, h, c;
+
+ hr = Math.atan2(b, a);
+ h = hr * 360 / 2 / Math.PI;
+ if (h < 0) {
+ h += 360;
+ }
+ c = Math.sqrt(a * a + b * b);
+ return [l, c, h];
+}
+
+function lab2rgb(args) {
+ return xyz2rgb(lab2xyz(args));
+}
+
+function lch2lab(lch) {
+ var l = lch[0],
+ c = lch[1],
+ h = lch[2],
+ a, b, hr;
+
+ hr = h / 360 * 2 * Math.PI;
+ a = c * Math.cos(hr);
+ b = c * Math.sin(hr);
+ return [l, a, b];
+}
+
+function lch2xyz(args) {
+ return lab2xyz(lch2lab(args));
+}
+
+function lch2rgb(args) {
+ return lab2rgb(lch2lab(args));
+}
+
+function keyword2rgb(keyword) {
+ return cssKeywords[keyword];
+}
+
+function keyword2hsl(args) {
+ return rgb2hsl(keyword2rgb(args));
+}
+
+function keyword2hsv(args) {
+ return rgb2hsv(keyword2rgb(args));
+}
+
+function keyword2hwb(args) {
+ return rgb2hwb(keyword2rgb(args));
+}
+
+function keyword2cmyk(args) {
+ return rgb2cmyk(keyword2rgb(args));
+}
+
+function keyword2lab(args) {
+ return rgb2lab(keyword2rgb(args));
+}
+
+function keyword2xyz(args) {
+ return rgb2xyz(keyword2rgb(args));
+}
+
+var cssKeywords = {
+ aliceblue: [240,248,255],
+ antiquewhite: [250,235,215],
+ aqua: [0,255,255],
+ aquamarine: [127,255,212],
+ azure: [240,255,255],
+ beige: [245,245,220],
+ bisque: [255,228,196],
+ black: [0,0,0],
+ blanchedalmond: [255,235,205],
+ blue: [0,0,255],
+ blueviolet: [138,43,226],
+ brown: [165,42,42],
+ burlywood: [222,184,135],
+ cadetblue: [95,158,160],
+ chartreuse: [127,255,0],
+ chocolate: [210,105,30],
+ coral: [255,127,80],
+ cornflowerblue: [100,149,237],
+ cornsilk: [255,248,220],
+ crimson: [220,20,60],
+ cyan: [0,255,255],
+ darkblue: [0,0,139],
+ darkcyan: [0,139,139],
+ darkgoldenrod: [184,134,11],
+ darkgray: [169,169,169],
+ darkgreen: [0,100,0],
+ darkgrey: [169,169,169],
+ darkkhaki: [189,183,107],
+ darkmagenta: [139,0,139],
+ darkolivegreen: [85,107,47],
+ darkorange: [255,140,0],
+ darkorchid: [153,50,204],
+ darkred: [139,0,0],
+ darksalmon: [233,150,122],
+ darkseagreen: [143,188,143],
+ darkslateblue: [72,61,139],
+ darkslategray: [47,79,79],
+ darkslategrey: [47,79,79],
+ darkturquoise: [0,206,209],
+ darkviolet: [148,0,211],
+ deeppink: [255,20,147],
+ deepskyblue: [0,191,255],
+ dimgray: [105,105,105],
+ dimgrey: [105,105,105],
+ dodgerblue: [30,144,255],
+ firebrick: [178,34,34],
+ floralwhite: [255,250,240],
+ forestgreen: [34,139,34],
+ fuchsia: [255,0,255],
+ gainsboro: [220,220,220],
+ ghostwhite: [248,248,255],
+ gold: [255,215,0],
+ goldenrod: [218,165,32],
+ gray: [128,128,128],
+ green: [0,128,0],
+ greenyellow: [173,255,47],
+ grey: [128,128,128],
+ honeydew: [240,255,240],
+ hotpink: [255,105,180],
+ indianred: [205,92,92],
+ indigo: [75,0,130],
+ ivory: [255,255,240],
+ khaki: [240,230,140],
+ lavender: [230,230,250],
+ lavenderblush: [255,240,245],
+ lawngreen: [124,252,0],
+ lemonchiffon: [255,250,205],
+ lightblue: [173,216,230],
+ lightcoral: [240,128,128],
+ lightcyan: [224,255,255],
+ lightgoldenrodyellow: [250,250,210],
+ lightgray: [211,211,211],
+ lightgreen: [144,238,144],
+ lightgrey: [211,211,211],
+ lightpink: [255,182,193],
+ lightsalmon: [255,160,122],
+ lightseagreen: [32,178,170],
+ lightskyblue: [135,206,250],
+ lightslategray: [119,136,153],
+ lightslategrey: [119,136,153],
+ lightsteelblue: [176,196,222],
+ lightyellow: [255,255,224],
+ lime: [0,255,0],
+ limegreen: [50,205,50],
+ linen: [250,240,230],
+ magenta: [255,0,255],
+ maroon: [128,0,0],
+ mediumaquamarine: [102,205,170],
+ mediumblue: [0,0,205],
+ mediumorchid: [186,85,211],
+ mediumpurple: [147,112,219],
+ mediumseagreen: [60,179,113],
+ mediumslateblue: [123,104,238],
+ mediumspringgreen: [0,250,154],
+ mediumturquoise: [72,209,204],
+ mediumvioletred: [199,21,133],
+ midnightblue: [25,25,112],
+ mintcream: [245,255,250],
+ mistyrose: [255,228,225],
+ moccasin: [255,228,181],
+ navajowhite: [255,222,173],
+ navy: [0,0,128],
+ oldlace: [253,245,230],
+ olive: [128,128,0],
+ olivedrab: [107,142,35],
+ orange: [255,165,0],
+ orangered: [255,69,0],
+ orchid: [218,112,214],
+ palegoldenrod: [238,232,170],
+ palegreen: [152,251,152],
+ paleturquoise: [175,238,238],
+ palevioletred: [219,112,147],
+ papayawhip: [255,239,213],
+ peachpuff: [255,218,185],
+ peru: [205,133,63],
+ pink: [255,192,203],
+ plum: [221,160,221],
+ powderblue: [176,224,230],
+ purple: [128,0,128],
+ rebeccapurple: [102, 51, 153],
+ red: [255,0,0],
+ rosybrown: [188,143,143],
+ royalblue: [65,105,225],
+ saddlebrown: [139,69,19],
+ salmon: [250,128,114],
+ sandybrown: [244,164,96],
+ seagreen: [46,139,87],
+ seashell: [255,245,238],
+ sienna: [160,82,45],
+ silver: [192,192,192],
+ skyblue: [135,206,235],
+ slateblue: [106,90,205],
+ slategray: [112,128,144],
+ slategrey: [112,128,144],
+ snow: [255,250,250],
+ springgreen: [0,255,127],
+ steelblue: [70,130,180],
+ tan: [210,180,140],
+ teal: [0,128,128],
+ thistle: [216,191,216],
+ tomato: [255,99,71],
+ turquoise: [64,224,208],
+ violet: [238,130,238],
+ wheat: [245,222,179],
+ white: [255,255,255],
+ whitesmoke: [245,245,245],
+ yellow: [255,255,0],
+ yellowgreen: [154,205,50]
+};
+
+var reverseKeywords = {};
+for (var key in cssKeywords) {
+ reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
+}
+
+},{}],5:[function(require,module,exports){
+var conversions = require(4);
+
+var convert = function() {
+ return new Converter();
+}
+
+for (var func in conversions) {
+ // export Raw versions
+ convert[func + "Raw"] = (function(func) {
+ // accept array or plain args
+ return function(arg) {
+ if (typeof arg == "number")
+ arg = Array.prototype.slice.call(arguments);
+ return conversions[func](arg);
+ }
+ })(func);
+
+ var pair = /(\w+)2(\w+)/.exec(func),
+ from = pair[1],
+ to = pair[2];
+
+ // export rgb2hsl and ["rgb"]["hsl"]
+ convert[from] = convert[from] || {};
+
+ convert[from][to] = convert[func] = (function(func) {
+ return function(arg) {
+ if (typeof arg == "number")
+ arg = Array.prototype.slice.call(arguments);
+
+ var val = conversions[func](arg);
+ if (typeof val == "string" || val === undefined)
+ return val; // keyword
+
+ for (var i = 0; i < val.length; i++)
+ val[i] = Math.round(val[i]);
+ return val;
+ }
+ })(func);
+}
+
+
+/* Converter does lazy conversion and caching */
+var Converter = function() {
+ this.convs = {};
+};
+
+/* Either get the values for a space or
+ set the values for a space, depending on args */
+Converter.prototype.routeSpace = function(space, args) {
+ var values = args[0];
+ if (values === undefined) {
+ // color.rgb()
+ return this.getValues(space);
+ }
+ // color.rgb(10, 10, 10)
+ if (typeof values == "number") {
+ values = Array.prototype.slice.call(args);
+ }
+
+ return this.setValues(space, values);
+};
+
+/* Set the values for a space, invalidating cache */
+Converter.prototype.setValues = function(space, values) {
+ this.space = space;
+ this.convs = {};
+ this.convs[space] = values;
+ return this;
+};
+
+/* Get the values for a space. If there's already
+ a conversion for the space, fetch it, otherwise
+ compute it */
+Converter.prototype.getValues = function(space) {
+ var vals = this.convs[space];
+ if (!vals) {
+ var fspace = this.space,
+ from = this.convs[fspace];
+ vals = convert[fspace][space](from);
+
+ this.convs[space] = vals;
+ }
+ return vals;
+};
+
+["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
+ Converter.prototype[space] = function(vals) {
+ return this.routeSpace(space, arguments);
+ }
+});
+
+module.exports = convert;
+},{"4":4}],6:[function(require,module,exports){
+module.exports = {
+ "aliceblue": [240, 248, 255],
+ "antiquewhite": [250, 235, 215],
+ "aqua": [0, 255, 255],
+ "aquamarine": [127, 255, 212],
+ "azure": [240, 255, 255],
+ "beige": [245, 245, 220],
+ "bisque": [255, 228, 196],
+ "black": [0, 0, 0],
+ "blanchedalmond": [255, 235, 205],
+ "blue": [0, 0, 255],
+ "blueviolet": [138, 43, 226],
+ "brown": [165, 42, 42],
+ "burlywood": [222, 184, 135],
+ "cadetblue": [95, 158, 160],
+ "chartreuse": [127, 255, 0],
+ "chocolate": [210, 105, 30],
+ "coral": [255, 127, 80],
+ "cornflowerblue": [100, 149, 237],
+ "cornsilk": [255, 248, 220],
+ "crimson": [220, 20, 60],
+ "cyan": [0, 255, 255],
+ "darkblue": [0, 0, 139],
+ "darkcyan": [0, 139, 139],
+ "darkgoldenrod": [184, 134, 11],
+ "darkgray": [169, 169, 169],
+ "darkgreen": [0, 100, 0],
+ "darkgrey": [169, 169, 169],
+ "darkkhaki": [189, 183, 107],
+ "darkmagenta": [139, 0, 139],
+ "darkolivegreen": [85, 107, 47],
+ "darkorange": [255, 140, 0],
+ "darkorchid": [153, 50, 204],
+ "darkred": [139, 0, 0],
+ "darksalmon": [233, 150, 122],
+ "darkseagreen": [143, 188, 143],
+ "darkslateblue": [72, 61, 139],
+ "darkslategray": [47, 79, 79],
+ "darkslategrey": [47, 79, 79],
+ "darkturquoise": [0, 206, 209],
+ "darkviolet": [148, 0, 211],
+ "deeppink": [255, 20, 147],
+ "deepskyblue": [0, 191, 255],
+ "dimgray": [105, 105, 105],
+ "dimgrey": [105, 105, 105],
+ "dodgerblue": [30, 144, 255],
+ "firebrick": [178, 34, 34],
+ "floralwhite": [255, 250, 240],
+ "forestgreen": [34, 139, 34],
+ "fuchsia": [255, 0, 255],
+ "gainsboro": [220, 220, 220],
+ "ghostwhite": [248, 248, 255],
+ "gold": [255, 215, 0],
+ "goldenrod": [218, 165, 32],
+ "gray": [128, 128, 128],
+ "green": [0, 128, 0],
+ "greenyellow": [173, 255, 47],
+ "grey": [128, 128, 128],
+ "honeydew": [240, 255, 240],
+ "hotpink": [255, 105, 180],
+ "indianred": [205, 92, 92],
+ "indigo": [75, 0, 130],
+ "ivory": [255, 255, 240],
+ "khaki": [240, 230, 140],
+ "lavender": [230, 230, 250],
+ "lavenderblush": [255, 240, 245],
+ "lawngreen": [124, 252, 0],
+ "lemonchiffon": [255, 250, 205],
+ "lightblue": [173, 216, 230],
+ "lightcoral": [240, 128, 128],
+ "lightcyan": [224, 255, 255],
+ "lightgoldenrodyellow": [250, 250, 210],
+ "lightgray": [211, 211, 211],
+ "lightgreen": [144, 238, 144],
+ "lightgrey": [211, 211, 211],
+ "lightpink": [255, 182, 193],
+ "lightsalmon": [255, 160, 122],
+ "lightseagreen": [32, 178, 170],
+ "lightskyblue": [135, 206, 250],
+ "lightslategray": [119, 136, 153],
+ "lightslategrey": [119, 136, 153],
+ "lightsteelblue": [176, 196, 222],
+ "lightyellow": [255, 255, 224],
+ "lime": [0, 255, 0],
+ "limegreen": [50, 205, 50],
+ "linen": [250, 240, 230],
+ "magenta": [255, 0, 255],
+ "maroon": [128, 0, 0],
+ "mediumaquamarine": [102, 205, 170],
+ "mediumblue": [0, 0, 205],
+ "mediumorchid": [186, 85, 211],
+ "mediumpurple": [147, 112, 219],
+ "mediumseagreen": [60, 179, 113],
+ "mediumslateblue": [123, 104, 238],
+ "mediumspringgreen": [0, 250, 154],
+ "mediumturquoise": [72, 209, 204],
+ "mediumvioletred": [199, 21, 133],
+ "midnightblue": [25, 25, 112],
+ "mintcream": [245, 255, 250],
+ "mistyrose": [255, 228, 225],
+ "moccasin": [255, 228, 181],
+ "navajowhite": [255, 222, 173],
+ "navy": [0, 0, 128],
+ "oldlace": [253, 245, 230],
+ "olive": [128, 128, 0],
+ "olivedrab": [107, 142, 35],
+ "orange": [255, 165, 0],
+ "orangered": [255, 69, 0],
+ "orchid": [218, 112, 214],
+ "palegoldenrod": [238, 232, 170],
+ "palegreen": [152, 251, 152],
+ "paleturquoise": [175, 238, 238],
+ "palevioletred": [219, 112, 147],
+ "papayawhip": [255, 239, 213],
+ "peachpuff": [255, 218, 185],
+ "peru": [205, 133, 63],
+ "pink": [255, 192, 203],
+ "plum": [221, 160, 221],
+ "powderblue": [176, 224, 230],
+ "purple": [128, 0, 128],
+ "rebeccapurple": [102, 51, 153],
+ "red": [255, 0, 0],
+ "rosybrown": [188, 143, 143],
+ "royalblue": [65, 105, 225],
+ "saddlebrown": [139, 69, 19],
+ "salmon": [250, 128, 114],
+ "sandybrown": [244, 164, 96],
+ "seagreen": [46, 139, 87],
+ "seashell": [255, 245, 238],
+ "sienna": [160, 82, 45],
+ "silver": [192, 192, 192],
+ "skyblue": [135, 206, 235],
+ "slateblue": [106, 90, 205],
+ "slategray": [112, 128, 144],
+ "slategrey": [112, 128, 144],
+ "snow": [255, 250, 250],
+ "springgreen": [0, 255, 127],
+ "steelblue": [70, 130, 180],
+ "tan": [210, 180, 140],
+ "teal": [0, 128, 128],
+ "thistle": [216, 191, 216],
+ "tomato": [255, 99, 71],
+ "turquoise": [64, 224, 208],
+ "violet": [238, 130, 238],
+ "wheat": [245, 222, 179],
+ "white": [255, 255, 255],
+ "whitesmoke": [245, 245, 245],
+ "yellow": [255, 255, 0],
+ "yellowgreen": [154, 205, 50]
+};
+},{}],7:[function(require,module,exports){
+/**
+ * @namespace Chart
+ */
+var Chart = require(27)();
+
+require(26)(Chart);
+require(22)(Chart);
+require(25)(Chart);
+require(21)(Chart);
+require(23)(Chart);
+require(24)(Chart);
+require(28)(Chart);
+require(32)(Chart);
+require(30)(Chart);
+require(31)(Chart);
+require(33)(Chart);
+require(29)(Chart);
+require(34)(Chart);
+
+require(35)(Chart);
+require(36)(Chart);
+require(37)(Chart);
+require(38)(Chart);
+
+require(41)(Chart);
+require(39)(Chart);
+require(40)(Chart);
+require(42)(Chart);
+require(43)(Chart);
+require(44)(Chart);
+
+// Controllers must be loaded after elements
+// See Chart.core.datasetController.dataElementType
+require(15)(Chart);
+require(16)(Chart);
+require(17)(Chart);
+require(18)(Chart);
+require(19)(Chart);
+require(20)(Chart);
+
+require(8)(Chart);
+require(9)(Chart);
+require(10)(Chart);
+require(11)(Chart);
+require(12)(Chart);
+require(13)(Chart);
+require(14)(Chart);
+
+window.Chart = module.exports = Chart;
+
+},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"8":8,"9":9}],8:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Bar = function(context, config) {
+ config.type = 'bar';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],9:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Bubble = function(context, config) {
+ config.type = 'bubble';
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],10:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Doughnut = function(context, config) {
+ config.type = 'doughnut';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],11:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Line = function(context, config) {
+ config.type = 'line';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],12:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.PolarArea = function(context, config) {
+ config.type = 'polarArea';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],13:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Radar = function(context, config) {
+ config.options = Chart.helpers.configMerge({aspectRatio: 1}, config.options);
+ config.type = 'radar';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],14:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var defaultConfig = {
+ hover: {
+ mode: 'single'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'linear', // scatter should not use a category axis
+ position: 'bottom',
+ id: 'x-axis-1' // need an ID so datasets can reference the scale
+ }],
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-1'
+ }]
+ },
+
+ tooltips: {
+ callbacks: {
+ title: function() {
+ // Title doesn't make sense for scatter since we format the data as a point
+ return '';
+ },
+ label: function(tooltipItem) {
+ return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
+ }
+ }
+ }
+ };
+
+ // Register the default config for this type
+ Chart.defaults.scatter = defaultConfig;
+
+ // Scatter charts use line controllers
+ Chart.controllers.scatter = Chart.controllers.line;
+
+ Chart.Scatter = function(context, config) {
+ config.type = 'scatter';
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],15:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.bar = {
+ hover: {
+ mode: 'label'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'category',
+
+ // Specific to Bar Controller
+ categoryPercentage: 0.8,
+ barPercentage: 0.9,
+
+ // grid line settings
+ gridLines: {
+ offsetGridLines: true
+ }
+ }],
+ yAxes: [{
+ type: 'linear'
+ }]
+ }
+ };
+
+ Chart.controllers.bar = Chart.DatasetController.extend({
+
+ dataElementType: Chart.elements.Rectangle,
+
+ initialize: function(chart, datasetIndex) {
+ Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
+
+ // Use this to indicate that this is a bar dataset.
+ this.getMeta().bar = true;
+ },
+
+ // Get the number of datasets that display bars. We use this to correctly calculate the bar width
+ getBarCount: function() {
+ var me = this;
+ var barCount = 0;
+ helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
+ var meta = me.chart.getDatasetMeta(datasetIndex);
+ if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
+ ++barCount;
+ }
+ }, me);
+ return barCount;
+ },
+
+ update: function(reset) {
+ var me = this;
+ helpers.each(me.getMeta().data, function(rectangle, index) {
+ me.updateElement(rectangle, index, reset);
+ }, me);
+ },
+
+ updateElement: function(rectangle, index, reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var scaleBase = yScale.getBasePixel();
+ var rectangleElementOptions = me.chart.options.elements.rectangle;
+ var custom = rectangle.custom || {};
+ var dataset = me.getDataset();
+
+ helpers.extend(rectangle, {
+ // Utility
+ _xScale: xScale,
+ _yScale: yScale,
+ _datasetIndex: me.index,
+ _index: index,
+
+ // Desired view properties
+ _model: {
+ x: me.calculateBarX(index, me.index),
+ y: reset ? scaleBase : me.calculateBarY(index, me.index),
+
+ // Tooltip
+ label: me.chart.data.labels[index],
+ datasetLabel: dataset.label,
+
+ // Appearance
+ base: reset ? scaleBase : me.calculateBarBase(me.index, index),
+ width: me.calculateBarWidth(index),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
+ borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
+ borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
+ }
+ });
+ rectangle.pivot();
+ },
+
+ calculateBarBase: function(datasetIndex, index) {
+ var me = this;
+ var meta = me.getMeta();
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var base = 0;
+
+ if (yScale.options.stacked) {
+ var chart = me.chart;
+ var datasets = chart.data.datasets;
+ var value = Number(datasets[datasetIndex].data[index]);
+
+ for (var i = 0; i < datasetIndex; i++) {
+ var currentDs = datasets[i];
+ var currentDsMeta = chart.getDatasetMeta(i);
+ if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
+ var currentVal = Number(currentDs.data[index]);
+ base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
+ }
+ }
+
+ return yScale.getPixelForValue(base);
+ }
+
+ return yScale.getBasePixel();
+ },
+
+ getRuler: function(index) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var datasetCount = me.getBarCount();
+
+ var tickWidth;
+
+ if (xScale.options.type === 'category') {
+ tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
+ } else {
+ // Average width
+ tickWidth = xScale.width / xScale.ticks.length;
+ }
+ var categoryWidth = tickWidth * xScale.options.categoryPercentage;
+ var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
+ var fullBarWidth = categoryWidth / datasetCount;
+
+ if (xScale.ticks.length !== me.chart.data.labels.length) {
+ var perc = xScale.ticks.length / me.chart.data.labels.length;
+ fullBarWidth = fullBarWidth * perc;
+ }
+
+ var barWidth = fullBarWidth * xScale.options.barPercentage;
+ var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
+
+ return {
+ datasetCount: datasetCount,
+ tickWidth: tickWidth,
+ categoryWidth: categoryWidth,
+ categorySpacing: categorySpacing,
+ fullBarWidth: fullBarWidth,
+ barWidth: barWidth,
+ barSpacing: barSpacing
+ };
+ },
+
+ calculateBarWidth: function(index) {
+ var xScale = this.getScaleForId(this.getMeta().xAxisID);
+ if (xScale.options.barThickness) {
+ return xScale.options.barThickness;
+ }
+ var ruler = this.getRuler(index);
+ return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
+ },
+
+ // Get bar index from the given dataset index accounting for the fact that not all bars are visible
+ getBarIndex: function(datasetIndex) {
+ var barIndex = 0;
+ var meta, j;
+
+ for (j = 0; j < datasetIndex; ++j) {
+ meta = this.chart.getDatasetMeta(j);
+ if (meta.bar && this.chart.isDatasetVisible(j)) {
+ ++barIndex;
+ }
+ }
+
+ return barIndex;
+ },
+
+ calculateBarX: function(index, datasetIndex) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var barIndex = me.getBarIndex(datasetIndex);
+
+ var ruler = me.getRuler(index);
+ var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
+ leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;
+
+ if (xScale.options.stacked) {
+ return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
+ }
+
+ return leftTick +
+ (ruler.barWidth / 2) +
+ ruler.categorySpacing +
+ (ruler.barWidth * barIndex) +
+ (ruler.barSpacing / 2) +
+ (ruler.barSpacing * barIndex);
+ },
+
+ calculateBarY: function(index, datasetIndex) {
+ var me = this;
+ var meta = me.getMeta();
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var value = Number(me.getDataset().data[index]);
+
+ if (yScale.options.stacked) {
+
+ var sumPos = 0,
+ sumNeg = 0;
+
+ for (var i = 0; i < datasetIndex; i++) {
+ var ds = me.chart.data.datasets[i];
+ var dsMeta = me.chart.getDatasetMeta(i);
+ if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) {
+ var stackedVal = Number(ds.data[index]);
+ if (stackedVal < 0) {
+ sumNeg += stackedVal || 0;
+ } else {
+ sumPos += stackedVal || 0;
+ }
+ }
+ }
+
+ if (value < 0) {
+ return yScale.getPixelForValue(sumNeg + value);
+ }
+ return yScale.getPixelForValue(sumPos + value);
+ }
+
+ return yScale.getPixelForValue(value);
+ },
+
+ draw: function(ease) {
+ var me = this;
+ var easingDecimal = ease || 1;
+ helpers.each(me.getMeta().data, function(rectangle, index) {
+ var d = me.getDataset().data[index];
+ if (d !== null && d !== undefined && !isNaN(d)) {
+ rectangle.transition(easingDecimal).draw();
+ }
+ }, me);
+ },
+
+ setHoverStyle: function(rectangle) {
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
+ var index = rectangle._index;
+
+ var custom = rectangle.custom || {};
+ var model = rectangle._model;
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
+ },
+
+ removeHoverStyle: function(rectangle) {
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
+ var index = rectangle._index;
+ var custom = rectangle.custom || {};
+ var model = rectangle._model;
+ var rectangleElementOptions = this.chart.options.elements.rectangle;
+
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
+ }
+
+ });
+
+
+ // including horizontalBar in the bar file, instead of a file of its own
+ // it extends bar (like pie extends doughnut)
+ Chart.defaults.horizontalBar = {
+ hover: {
+ mode: 'label'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'linear',
+ position: 'bottom'
+ }],
+ yAxes: [{
+ position: 'left',
+ type: 'category',
+
+ // Specific to Horizontal Bar Controller
+ categoryPercentage: 0.8,
+ barPercentage: 0.9,
+
+ // grid line settings
+ gridLines: {
+ offsetGridLines: true
+ }
+ }]
+ },
+ elements: {
+ rectangle: {
+ borderSkipped: 'left'
+ }
+ },
+ tooltips: {
+ callbacks: {
+ title: function(tooltipItems, data) {
+ // Pick first xLabel for now
+ var title = '';
+
+ if (tooltipItems.length > 0) {
+ if (tooltipItems[0].yLabel) {
+ title = tooltipItems[0].yLabel;
+ } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
+ title = data.labels[tooltipItems[0].index];
+ }
+ }
+
+ return title;
+ },
+ label: function(tooltipItem, data) {
+ var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
+ return datasetLabel + ': ' + tooltipItem.xLabel;
+ }
+ }
+ }
+ };
+
+ Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
+ updateElement: function(rectangle, index, reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var scaleBase = xScale.getBasePixel();
+ var custom = rectangle.custom || {};
+ var dataset = me.getDataset();
+ var rectangleElementOptions = me.chart.options.elements.rectangle;
+
+ helpers.extend(rectangle, {
+ // Utility
+ _xScale: xScale,
+ _yScale: yScale,
+ _datasetIndex: me.index,
+ _index: index,
+
+ // Desired view properties
+ _model: {
+ x: reset ? scaleBase : me.calculateBarX(index, me.index),
+ y: me.calculateBarY(index, me.index),
+
+ // Tooltip
+ label: me.chart.data.labels[index],
+ datasetLabel: dataset.label,
+
+ // Appearance
+ base: reset ? scaleBase : me.calculateBarBase(me.index, index),
+ height: me.calculateBarHeight(index),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
+ borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
+ borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
+ },
+
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ var halfHeight = vm.height / 2,
+ topY = vm.y - halfHeight,
+ bottomY = vm.y + halfHeight,
+ right = vm.base - (vm.base - vm.x),
+ halfStroke = vm.borderWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (vm.borderWidth) {
+ topY += halfStroke;
+ bottomY -= halfStroke;
+ right += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = vm.backgroundColor;
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = vm.borderWidth;
+
+ // Corner points, from bottom-left to bottom-right clockwise
+ // | 1 2 |
+ // | 0 3 |
+ var corners = [
+ [vm.base, bottomY],
+ [vm.base, topY],
+ [right, topY],
+ [right, bottomY]
+ ];
+
+ // Find first (starting) corner with fallback to 'bottom'
+ var borders = ['bottom', 'left', 'top', 'right'];
+ var startCorner = borders.indexOf(vm.borderSkipped, 0);
+ if (startCorner === -1) {
+ startCorner = 0;
+ }
+
+ function cornerAt(cornerIndex) {
+ return corners[(startCorner + cornerIndex) % 4];
+ }
+
+ // Draw rectangle from 'startCorner'
+ ctx.moveTo.apply(ctx, cornerAt(0));
+ for (var i = 1; i < 4; i++) {
+ ctx.lineTo.apply(ctx, cornerAt(i));
+ }
+
+ ctx.fill();
+ if (vm.borderWidth) {
+ ctx.stroke();
+ }
+ },
+
+ inRange: function(mouseX, mouseY) {
+ var vm = this._view;
+ var inRange = false;
+
+ if (vm) {
+ if (vm.x < vm.base) {
+ inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
+ } else {
+ inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
+ }
+ }
+
+ return inRange;
+ }
+ });
+
+ rectangle.pivot();
+ },
+
+ calculateBarBase: function(datasetIndex, index) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var base = 0;
+
+ if (xScale.options.stacked) {
+ var chart = me.chart;
+ var datasets = chart.data.datasets;
+ var value = Number(datasets[datasetIndex].data[index]);
+
+ for (var i = 0; i < datasetIndex; i++) {
+ var currentDs = datasets[i];
+ var currentDsMeta = chart.getDatasetMeta(i);
+ if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) {
+ var currentVal = Number(currentDs.data[index]);
+ base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
+ }
+ }
+
+ return xScale.getPixelForValue(base);
+ }
+
+ return xScale.getBasePixel();
+ },
+
+ getRuler: function(index) {
+ var me = this;
+ var meta = me.getMeta();
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var datasetCount = me.getBarCount();
+
+ var tickHeight;
+ if (yScale.options.type === 'category') {
+ tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
+ } else {
+ // Average width
+ tickHeight = yScale.width / yScale.ticks.length;
+ }
+ var categoryHeight = tickHeight * yScale.options.categoryPercentage;
+ var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
+ var fullBarHeight = categoryHeight / datasetCount;
+
+ if (yScale.ticks.length !== me.chart.data.labels.length) {
+ var perc = yScale.ticks.length / me.chart.data.labels.length;
+ fullBarHeight = fullBarHeight * perc;
+ }
+
+ var barHeight = fullBarHeight * yScale.options.barPercentage;
+ var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
+
+ return {
+ datasetCount: datasetCount,
+ tickHeight: tickHeight,
+ categoryHeight: categoryHeight,
+ categorySpacing: categorySpacing,
+ fullBarHeight: fullBarHeight,
+ barHeight: barHeight,
+ barSpacing: barSpacing
+ };
+ },
+
+ calculateBarHeight: function(index) {
+ var me = this;
+ var yScale = me.getScaleForId(me.getMeta().yAxisID);
+ if (yScale.options.barThickness) {
+ return yScale.options.barThickness;
+ }
+ var ruler = me.getRuler(index);
+ return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
+ },
+
+ calculateBarX: function(index, datasetIndex) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var value = Number(me.getDataset().data[index]);
+
+ if (xScale.options.stacked) {
+
+ var sumPos = 0,
+ sumNeg = 0;
+
+ for (var i = 0; i < datasetIndex; i++) {
+ var ds = me.chart.data.datasets[i];
+ var dsMeta = me.chart.getDatasetMeta(i);
+ if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) {
+ var stackedVal = Number(ds.data[index]);
+ if (stackedVal < 0) {
+ sumNeg += stackedVal || 0;
+ } else {
+ sumPos += stackedVal || 0;
+ }
+ }
+ }
+
+ if (value < 0) {
+ return xScale.getPixelForValue(sumNeg + value);
+ }
+ return xScale.getPixelForValue(sumPos + value);
+ }
+
+ return xScale.getPixelForValue(value);
+ },
+
+ calculateBarY: function(index, datasetIndex) {
+ var me = this;
+ var meta = me.getMeta();
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var barIndex = me.getBarIndex(datasetIndex);
+
+ var ruler = me.getRuler(index);
+ var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
+ topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
+
+ if (yScale.options.stacked) {
+ return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
+ }
+
+ return topTick +
+ (ruler.barHeight / 2) +
+ ruler.categorySpacing +
+ (ruler.barHeight * barIndex) +
+ (ruler.barSpacing / 2) +
+ (ruler.barSpacing * barIndex);
+ }
+ });
+};
+
+},{}],16:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.bubble = {
+ hover: {
+ mode: 'single'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'linear', // bubble should probably use a linear scale by default
+ position: 'bottom',
+ id: 'x-axis-0' // need an ID so datasets can reference the scale
+ }],
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-0'
+ }]
+ },
+
+ tooltips: {
+ callbacks: {
+ title: function() {
+ // Title doesn't make sense for scatter since we format the data as a point
+ return '';
+ },
+ label: function(tooltipItem, data) {
+ var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
+ var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
+ return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
+ }
+ }
+ }
+ };
+
+ Chart.controllers.bubble = Chart.DatasetController.extend({
+
+ dataElementType: Chart.elements.Point,
+
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var points = meta.data;
+
+ // Update Points
+ helpers.each(points, function(point, index) {
+ me.updateElement(point, index, reset);
+ });
+ },
+
+ updateElement: function(point, index, reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var yScale = me.getScaleForId(meta.yAxisID);
+
+ var custom = point.custom || {};
+ var dataset = me.getDataset();
+ var data = dataset.data[index];
+ var pointElementOptions = me.chart.options.elements.point;
+ var dsIndex = me.index;
+
+ helpers.extend(point, {
+ // Utility
+ _xScale: xScale,
+ _yScale: yScale,
+ _datasetIndex: dsIndex,
+ _index: index,
+
+ // Desired view properties
+ _model: {
+ x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo),
+ y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex),
+ // Appearance
+ radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data),
+
+ // Tooltip
+ hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
+ }
+ });
+
+ // Trick to reset the styles of the point
+ Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions);
+
+ var model = point._model;
+ model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y));
+
+ point.pivot();
+ },
+
+ getRadius: function(value) {
+ return value.r || this.chart.options.elements.point.radius;
+ },
+
+ setHoverStyle: function(point) {
+ var me = this;
+ Chart.DatasetController.prototype.setHoverStyle.call(me, point);
+
+ // Radius
+ var dataset = me.chart.data.datasets[point._datasetIndex];
+ var index = point._index;
+ var custom = point.custom || {};
+ var model = point._model;
+ model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]);
+ },
+
+ removeHoverStyle: function(point) {
+ var me = this;
+ Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point);
+
+ var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index];
+ var custom = point.custom || {};
+ var model = point._model;
+
+ model.radius = custom.radius ? custom.radius : me.getRadius(dataVal);
+ }
+ });
+};
+
+},{}],17:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers,
+ defaults = Chart.defaults;
+
+ defaults.doughnut = {
+ animation: {
+ // Boolean - Whether we animate the rotation of the Doughnut
+ animateRotate: true,
+ // Boolean - Whether we animate scaling the Doughnut from the centre
+ animateScale: false
+ },
+ aspectRatio: 1,
+ hover: {
+ mode: 'single'
+ },
+ legendCallback: function(chart) {
+ var text = [];
+ text.push('');
+
+ var data = chart.data;
+ var datasets = data.datasets;
+ var labels = data.labels;
+
+ if (datasets.length) {
+ for (var i = 0; i < datasets[0].data.length; ++i) {
+ text.push('- ');
+ if (labels[i]) {
+ text.push(labels[i]);
+ }
+ text.push('
');
+ }
+ }
+
+ text.push('
');
+ return text.join('');
+ },
+ legend: {
+ labels: {
+ generateLabels: function(chart) {
+ var data = chart.data;
+ if (data.labels.length && data.datasets.length) {
+ return data.labels.map(function(label, i) {
+ var meta = chart.getDatasetMeta(0);
+ var ds = data.datasets[0];
+ var arc = meta.data[i];
+ var custom = arc && arc.custom || {};
+ var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
+ var arcOpts = chart.options.elements.arc;
+ var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
+ var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
+ var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
+
+ return {
+ text: label,
+ fillStyle: fill,
+ strokeStyle: stroke,
+ lineWidth: bw,
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+
+ // Extra data used for toggling the correct item
+ index: i
+ };
+ });
+ }
+ return [];
+ }
+ },
+
+ onClick: function(e, legendItem) {
+ var index = legendItem.index;
+ var chart = this.chart;
+ var i, ilen, meta;
+
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
+ meta = chart.getDatasetMeta(i);
+ // toggle visibility of index if exists
+ if (meta.data[index]) {
+ meta.data[index].hidden = !meta.data[index].hidden;
+ }
+ }
+
+ chart.update();
+ }
+ },
+
+ // The percentage of the chart that we cut out of the middle.
+ cutoutPercentage: 50,
+
+ // The rotation of the chart, where the first data arc begins.
+ rotation: Math.PI * -0.5,
+
+ // The total circumference of the chart.
+ circumference: Math.PI * 2.0,
+
+ // Need to override these to give a nice default
+ tooltips: {
+ callbacks: {
+ title: function() {
+ return '';
+ },
+ label: function(tooltipItem, data) {
+ return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
+ }
+ }
+ }
+ };
+
+ defaults.pie = helpers.clone(defaults.doughnut);
+ helpers.extend(defaults.pie, {
+ cutoutPercentage: 0
+ });
+
+
+ Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
+
+ dataElementType: Chart.elements.Arc,
+
+ linkScales: helpers.noop,
+
+ // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
+ getRingIndex: function(datasetIndex) {
+ var ringIndex = 0;
+
+ for (var j = 0; j < datasetIndex; ++j) {
+ if (this.chart.isDatasetVisible(j)) {
+ ++ringIndex;
+ }
+ }
+
+ return ringIndex;
+ },
+
+ update: function(reset) {
+ var me = this;
+ var chart = me.chart,
+ chartArea = chart.chartArea,
+ opts = chart.options,
+ arcOpts = opts.elements.arc,
+ availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,
+ availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,
+ minSize = Math.min(availableWidth, availableHeight),
+ offset = {
+ x: 0,
+ y: 0
+ },
+ meta = me.getMeta(),
+ cutoutPercentage = opts.cutoutPercentage,
+ circumference = opts.circumference;
+
+ // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
+ if (circumference < Math.PI * 2.0) {
+ var startAngle = opts.rotation % (Math.PI * 2.0);
+ startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
+ var endAngle = startAngle + circumference;
+ var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
+ var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
+ var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
+ var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
+ var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
+ var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
+ var cutout = cutoutPercentage / 100.0;
+ var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
+ var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
+ var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
+ minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
+ offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
+ }
+
+ chart.borderWidth = me.getMaxBorderWidth(meta.data);
+ chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
+ chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
+ chart.offsetX = offset.x * chart.outerRadius;
+ chart.offsetY = offset.y * chart.outerRadius;
+
+ meta.total = me.calculateTotal();
+
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
+ me.innerRadius = me.outerRadius - chart.radiusLength;
+
+ helpers.each(meta.data, function(arc, index) {
+ me.updateElement(arc, index, reset);
+ });
+ },
+
+ updateElement: function(arc, index, reset) {
+ var me = this;
+ var chart = me.chart,
+ chartArea = chart.chartArea,
+ opts = chart.options,
+ animationOpts = opts.animation,
+ centerX = (chartArea.left + chartArea.right) / 2,
+ centerY = (chartArea.top + chartArea.bottom) / 2,
+ startAngle = opts.rotation, // non reset case handled later
+ endAngle = opts.rotation, // non reset case handled later
+ dataset = me.getDataset(),
+ circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),
+ innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius,
+ outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius,
+ valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
+
+ helpers.extend(arc, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+
+ // Desired view properties
+ _model: {
+ x: centerX + chart.offsetX,
+ y: centerY + chart.offsetY,
+ startAngle: startAngle,
+ endAngle: endAngle,
+ circumference: circumference,
+ outerRadius: outerRadius,
+ innerRadius: innerRadius,
+ label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
+ }
+ });
+
+ var model = arc._model;
+ // Resets the visual styles
+ this.removeHoverStyle(arc);
+
+ // Set correct angles if not resetting
+ if (!reset || !animationOpts.animateRotate) {
+ if (index === 0) {
+ model.startAngle = opts.rotation;
+ } else {
+ model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
+ }
+
+ model.endAngle = model.startAngle + model.circumference;
+ }
+
+ arc.pivot();
+ },
+
+ removeHoverStyle: function(arc) {
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
+ },
+
+ calculateTotal: function() {
+ var dataset = this.getDataset();
+ var meta = this.getMeta();
+ var total = 0;
+ var value;
+
+ helpers.each(meta.data, function(element, index) {
+ value = dataset.data[index];
+ if (!isNaN(value) && !element.hidden) {
+ total += Math.abs(value);
+ }
+ });
+
+ /* if (total === 0) {
+ total = NaN;
+ }*/
+
+ return total;
+ },
+
+ calculateCircumference: function(value) {
+ var total = this.getMeta().total;
+ if (total > 0 && !isNaN(value)) {
+ return (Math.PI * 2.0) * (value / total);
+ }
+ return 0;
+ },
+
+ // gets the max border or hover width to properly scale pie charts
+ getMaxBorderWidth: function(elements) {
+ var max = 0,
+ index = this.index,
+ length = elements.length,
+ borderWidth,
+ hoverWidth;
+
+ for (var i = 0; i < length; i++) {
+ borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0;
+ hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
+
+ max = borderWidth > max ? borderWidth : max;
+ max = hoverWidth > max ? hoverWidth : max;
+ }
+ return max;
+ }
+ });
+};
+
+},{}],18:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.line = {
+ showLines: true,
+ spanGaps: false,
+
+ hover: {
+ mode: 'label'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'category',
+ id: 'x-axis-0'
+ }],
+ yAxes: [{
+ type: 'linear',
+ id: 'y-axis-0'
+ }]
+ }
+ };
+
+ function lineEnabled(dataset, options) {
+ return helpers.getValueOrDefault(dataset.showLine, options.showLines);
+ }
+
+ Chart.controllers.line = Chart.DatasetController.extend({
+
+ datasetElementType: Chart.elements.Line,
+
+ dataElementType: Chart.elements.Point,
+
+ addElementAndReset: function(index) {
+ var me = this;
+ var options = me.chart.options;
+ var meta = me.getMeta();
+
+ Chart.DatasetController.prototype.addElementAndReset.call(me, index);
+
+ // Make sure bezier control points are updated
+ if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) {
+ me.updateBezierControlPoints();
+ }
+ },
+
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var line = meta.dataset;
+ var points = meta.data || [];
+ var options = me.chart.options;
+ var lineElementOptions = options.elements.line;
+ var scale = me.getScaleForId(meta.yAxisID);
+ var i, ilen, custom;
+ var dataset = me.getDataset();
+ var showLine = lineEnabled(dataset, options);
+
+ // Update Line
+ if (showLine) {
+ custom = line.custom || {};
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
+ dataset.lineTension = dataset.tension;
+ }
+
+ // Utility
+ line._scale = scale;
+ line._datasetIndex = me.index;
+ // Data
+ line._children = points;
+ // Model
+ line._model = {
+ // Appearance
+ // The default behavior of lines is to break at null values, according
+ // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
+ // This option gives linse the ability to span gaps
+ spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
+ tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
+ steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
+ cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
+ // Scale
+ scaleTop: scale.top,
+ scaleBottom: scale.bottom,
+ scaleZero: scale.getBasePixel()
+ };
+
+ line.pivot();
+ }
+
+ // Update Points
+ for (i=0, ilen=points.length; i');
+
+ var data = chart.data;
+ var datasets = data.datasets;
+ var labels = data.labels;
+
+ if (datasets.length) {
+ for (var i = 0; i < datasets[0].data.length; ++i) {
+ text.push('');
+ if (labels[i]) {
+ text.push(labels[i]);
+ }
+ text.push(' ');
+ }
+ }
+
+ text.push('');
+ return text.join('');
+ },
+ legend: {
+ labels: {
+ generateLabels: function(chart) {
+ var data = chart.data;
+ if (data.labels.length && data.datasets.length) {
+ return data.labels.map(function(label, i) {
+ var meta = chart.getDatasetMeta(0);
+ var ds = data.datasets[0];
+ var arc = meta.data[i];
+ var custom = arc.custom || {};
+ var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
+ var arcOpts = chart.options.elements.arc;
+ var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
+ var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
+ var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
+
+ return {
+ text: label,
+ fillStyle: fill,
+ strokeStyle: stroke,
+ lineWidth: bw,
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+
+ // Extra data used for toggling the correct item
+ index: i
+ };
+ });
+ }
+ return [];
+ }
+ },
+
+ onClick: function(e, legendItem) {
+ var index = legendItem.index;
+ var chart = this.chart;
+ var i, ilen, meta;
+
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
+ meta = chart.getDatasetMeta(i);
+ meta.data[index].hidden = !meta.data[index].hidden;
+ }
+
+ chart.update();
+ }
+ },
+
+ // Need to override these to give a nice default
+ tooltips: {
+ callbacks: {
+ title: function() {
+ return '';
+ },
+ label: function(tooltipItem, data) {
+ return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
+ }
+ }
+ }
+ };
+
+ Chart.controllers.polarArea = Chart.DatasetController.extend({
+
+ dataElementType: Chart.elements.Arc,
+
+ linkScales: helpers.noop,
+
+ update: function(reset) {
+ var me = this;
+ var chart = me.chart;
+ var chartArea = chart.chartArea;
+ var meta = me.getMeta();
+ var opts = chart.options;
+ var arcOpts = opts.elements.arc;
+ var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
+ chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
+ chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
+
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
+ me.innerRadius = me.outerRadius - chart.radiusLength;
+
+ meta.count = me.countVisibleElements();
+
+ helpers.each(meta.data, function(arc, index) {
+ me.updateElement(arc, index, reset);
+ });
+ },
+
+ updateElement: function(arc, index, reset) {
+ var me = this;
+ var chart = me.chart;
+ var dataset = me.getDataset();
+ var opts = chart.options;
+ var animationOpts = opts.animation;
+ var scale = chart.scale;
+ var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
+ var labels = chart.data.labels;
+
+ var circumference = me.calculateCircumference(dataset.data[index]);
+ var centerX = scale.xCenter;
+ var centerY = scale.yCenter;
+
+ // If there is NaN data before us, we need to calculate the starting angle correctly.
+ // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
+ var visibleCount = 0;
+ var meta = me.getMeta();
+ for (var i = 0; i < index; ++i) {
+ if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
+ ++visibleCount;
+ }
+ }
+
+ // var negHalfPI = -0.5 * Math.PI;
+ var datasetStartAngle = opts.startAngle;
+ var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
+ var startAngle = datasetStartAngle + (circumference * visibleCount);
+ var endAngle = startAngle + (arc.hidden ? 0 : circumference);
+
+ var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
+
+ helpers.extend(arc, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+ _scale: scale,
+
+ // Desired view properties
+ _model: {
+ x: centerX,
+ y: centerY,
+ innerRadius: 0,
+ outerRadius: reset ? resetRadius : distance,
+ startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
+ endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
+ label: getValueAtIndexOrDefault(labels, index, labels[index])
+ }
+ });
+
+ // Apply border and fill style
+ me.removeHoverStyle(arc);
+
+ arc.pivot();
+ },
+
+ removeHoverStyle: function(arc) {
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
+ },
+
+ countVisibleElements: function() {
+ var dataset = this.getDataset();
+ var meta = this.getMeta();
+ var count = 0;
+
+ helpers.each(meta.data, function(element, index) {
+ if (!isNaN(dataset.data[index]) && !element.hidden) {
+ count++;
+ }
+ });
+
+ return count;
+ },
+
+ calculateCircumference: function(value) {
+ var count = this.getMeta().count;
+ if (count > 0 && !isNaN(value)) {
+ return (2 * Math.PI) / count;
+ }
+ return 0;
+ }
+ });
+};
+
+},{}],20:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.radar = {
+ scale: {
+ type: 'radialLinear'
+ },
+ elements: {
+ line: {
+ tension: 0 // no bezier in radar
+ }
+ }
+ };
+
+ Chart.controllers.radar = Chart.DatasetController.extend({
+
+ datasetElementType: Chart.elements.Line,
+
+ dataElementType: Chart.elements.Point,
+
+ linkScales: helpers.noop,
+
+ addElementAndReset: function(index) {
+ Chart.DatasetController.prototype.addElementAndReset.call(this, index);
+
+ // Make sure bezier control points are updated
+ this.updateBezierControlPoints();
+ },
+
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var line = meta.dataset;
+ var points = meta.data;
+ var custom = line.custom || {};
+ var dataset = me.getDataset();
+ var lineElementOptions = me.chart.options.elements.line;
+ var scale = me.chart.scale;
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
+ dataset.lineTension = dataset.tension;
+ }
+
+ helpers.extend(meta.dataset, {
+ // Utility
+ _datasetIndex: me.index,
+ // Data
+ _children: points,
+ _loop: true,
+ // Model
+ _model: {
+ // Appearance
+ tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
+
+ // Scale
+ scaleTop: scale.top,
+ scaleBottom: scale.bottom,
+ scaleZero: scale.getBasePosition()
+ }
+ });
+
+ meta.dataset.pivot();
+
+ // Update Points
+ helpers.each(points, function(point, index) {
+ me.updateElement(point, index, reset);
+ }, me);
+
+
+ // Update bezier control points
+ me.updateBezierControlPoints();
+ },
+ updateElement: function(point, index, reset) {
+ var me = this;
+ var custom = point.custom || {};
+ var dataset = me.getDataset();
+ var scale = me.chart.scale;
+ var pointElementOptions = me.chart.options.elements.point;
+ var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
+
+ helpers.extend(point, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+ _scale: scale,
+
+ // Desired view properties
+ _model: {
+ x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
+ y: reset ? scale.yCenter : pointPosition.y,
+
+ // Appearance
+ tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension),
+ radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
+ borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
+ pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
+
+ // Tooltip
+ hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
+ }
+ });
+
+ point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
+ },
+ updateBezierControlPoints: function() {
+ var chartArea = this.chart.chartArea;
+ var meta = this.getMeta();
+
+ helpers.each(meta.data, function(point, index) {
+ var model = point._model;
+ var controlPoints = helpers.splineCurve(
+ helpers.previousItem(meta.data, index, true)._model,
+ model,
+ helpers.nextItem(meta.data, index, true)._model,
+ model.tension
+ );
+
+ // Prevent the bezier going outside of the bounds of the graph
+ model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
+ model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
+
+ model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
+ model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
+
+ // Now pivot the point for animation
+ point.pivot();
+ });
+ },
+
+ draw: function(ease) {
+ var meta = this.getMeta();
+ var easingDecimal = ease || 1;
+
+ // Transition Point Locations
+ helpers.each(meta.data, function(point) {
+ point.transition(easingDecimal);
+ });
+
+ // Transition and Draw the line
+ meta.dataset.transition(easingDecimal).draw();
+
+ // Draw the points
+ helpers.each(meta.data, function(point) {
+ point.draw();
+ });
+ },
+
+ setHoverStyle: function(point) {
+ // Point
+ var dataset = this.chart.data.datasets[point._datasetIndex];
+ var custom = point.custom || {};
+ var index = point._index;
+ var model = point._model;
+
+ model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
+ },
+
+ removeHoverStyle: function(point) {
+ var dataset = this.chart.data.datasets[point._datasetIndex];
+ var custom = point.custom || {};
+ var index = point._index;
+ var model = point._model;
+ var pointElementOptions = this.chart.options.elements.point;
+
+ model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius);
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
+ }
+ });
+};
+
+},{}],21:[function(require,module,exports){
+/* global window: false */
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.global.animation = {
+ duration: 1000,
+ easing: 'easeOutQuart',
+ onProgress: helpers.noop,
+ onComplete: helpers.noop
+ };
+
+ Chart.Animation = Chart.Element.extend({
+ currentStep: null, // the current animation step
+ numSteps: 60, // default number of steps
+ easing: '', // the easing to use for this animation
+ render: null, // render function used by the animation service
+
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
+ onAnimationComplete: null // user specified callback to fire when the animation finishes
+ });
+
+ Chart.animationService = {
+ frameDuration: 17,
+ animations: [],
+ dropFrames: 0,
+ request: null,
+ addAnimation: function(chartInstance, animationObject, duration, lazy) {
+ var me = this;
+
+ if (!lazy) {
+ chartInstance.animating = true;
+ }
+
+ for (var index = 0; index < me.animations.length; ++index) {
+ if (me.animations[index].chartInstance === chartInstance) {
+ // replacing an in progress animation
+ me.animations[index].animationObject = animationObject;
+ return;
+ }
+ }
+
+ me.animations.push({
+ chartInstance: chartInstance,
+ animationObject: animationObject
+ });
+
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
+ if (me.animations.length === 1) {
+ me.requestAnimationFrame();
+ }
+ },
+ // Cancel the animation for a given chart instance
+ cancelAnimation: function(chartInstance) {
+ var index = helpers.findIndex(this.animations, function(animationWrapper) {
+ return animationWrapper.chartInstance === chartInstance;
+ });
+
+ if (index !== -1) {
+ this.animations.splice(index, 1);
+ chartInstance.animating = false;
+ }
+ },
+ requestAnimationFrame: function() {
+ var me = this;
+ if (me.request === null) {
+ // Skip animation frame requests until the active one is executed.
+ // This can happen when processing mouse events, e.g. 'mousemove'
+ // and 'mouseout' events will trigger multiple renders.
+ me.request = helpers.requestAnimFrame.call(window, function() {
+ me.request = null;
+ me.startDigest();
+ });
+ }
+ },
+ startDigest: function() {
+ var me = this;
+
+ var startTime = Date.now();
+ var framesToDrop = 0;
+
+ if (me.dropFrames > 1) {
+ framesToDrop = Math.floor(me.dropFrames);
+ me.dropFrames = me.dropFrames % 1;
+ }
+
+ var i = 0;
+ while (i < me.animations.length) {
+ if (me.animations[i].animationObject.currentStep === null) {
+ me.animations[i].animationObject.currentStep = 0;
+ }
+
+ me.animations[i].animationObject.currentStep += 1 + framesToDrop;
+
+ if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) {
+ me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps;
+ }
+
+ me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject);
+ if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) {
+ me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]);
+ }
+
+ if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) {
+ if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) {
+ me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]);
+ }
+
+ // executed the last frame. Remove the animation.
+ me.animations[i].chartInstance.animating = false;
+
+ me.animations.splice(i, 1);
+ } else {
+ ++i;
+ }
+ }
+
+ var endTime = Date.now();
+ var dropFrames = (endTime - startTime) / me.frameDuration;
+
+ me.dropFrames += dropFrames;
+
+ // Do we have more stuff to animate?
+ if (me.animations.length > 0) {
+ me.requestAnimationFrame();
+ }
+ }
+ };
+};
+
+},{}],22:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+ // Global Chart canvas helpers object for drawing items to canvas
+ var helpers = Chart.canvasHelpers = {};
+
+ helpers.drawPoint = function(ctx, pointStyle, radius, x, y) {
+ var type, edgeLength, xOffset, yOffset, height, size;
+
+ if (typeof pointStyle === 'object') {
+ type = pointStyle.toString();
+ if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
+ ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
+ return;
+ }
+ }
+
+ if (isNaN(radius) || radius <= 0) {
+ return;
+ }
+
+ switch (pointStyle) {
+ // Default includes circle
+ default:
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'triangle':
+ ctx.beginPath();
+ edgeLength = 3 * radius / Math.sqrt(3);
+ height = edgeLength * Math.sqrt(3) / 2;
+ ctx.moveTo(x - edgeLength / 2, y + height / 3);
+ ctx.lineTo(x + edgeLength / 2, y + height / 3);
+ ctx.lineTo(x, y - 2 * height / 3);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'rect':
+ size = 1 / Math.SQRT2 * radius;
+ ctx.beginPath();
+ ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
+ ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
+ break;
+ case 'rectRot':
+ size = 1 / Math.SQRT2 * radius;
+ ctx.beginPath();
+ ctx.moveTo(x - size, y);
+ ctx.lineTo(x, y + size);
+ ctx.lineTo(x + size, y);
+ ctx.lineTo(x, y - size);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'cross':
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y - radius);
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ case 'crossRot':
+ ctx.beginPath();
+ xOffset = Math.cos(Math.PI / 4) * radius;
+ yOffset = Math.sin(Math.PI / 4) * radius;
+ ctx.moveTo(x - xOffset, y - yOffset);
+ ctx.lineTo(x + xOffset, y + yOffset);
+ ctx.moveTo(x - xOffset, y + yOffset);
+ ctx.lineTo(x + xOffset, y - yOffset);
+ ctx.closePath();
+ break;
+ case 'star':
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y - radius);
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ xOffset = Math.cos(Math.PI / 4) * radius;
+ yOffset = Math.sin(Math.PI / 4) * radius;
+ ctx.moveTo(x - xOffset, y - yOffset);
+ ctx.lineTo(x + xOffset, y + yOffset);
+ ctx.moveTo(x - xOffset, y + yOffset);
+ ctx.lineTo(x + xOffset, y - yOffset);
+ ctx.closePath();
+ break;
+ case 'line':
+ ctx.beginPath();
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ case 'dash':
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ }
+
+ ctx.stroke();
+ };
+};
+
+},{}],23:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ // Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ // Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ // Controllers available for dataset visualization eg. bar, line, slice, etc.
+ Chart.controllers = {};
+
+ /**
+ * @class Chart.Controller
+ * The main controller of a chart.
+ */
+ Chart.Controller = function(instance) {
+
+ this.chart = instance;
+ this.config = instance.config;
+ this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
+ this.id = helpers.uid();
+
+ Object.defineProperty(this, 'data', {
+ get: function() {
+ return this.config.data;
+ }
+ });
+
+ // Add the chart instance to the global namespace
+ Chart.instances[this.id] = this;
+
+ if (this.options.responsive) {
+ // Silent resize before chart draws
+ this.resize(true);
+ }
+
+ this.initialize();
+
+ return this;
+ };
+
+ helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
+
+ initialize: function() {
+ var me = this;
+ // Before init plugin notification
+ Chart.plugins.notify('beforeInit', [me]);
+
+ me.bindEvents();
+
+ // Make sure controllers are built first so that each dataset is bound to an axis before the scales
+ // are built
+ me.ensureScalesHaveIDs();
+ me.buildOrUpdateControllers();
+ me.buildScales();
+ me.updateLayout();
+ me.resetElements();
+ me.initToolTip();
+ me.update();
+
+ // After init plugin notification
+ Chart.plugins.notify('afterInit', [me]);
+
+ return me;
+ },
+
+ clear: function() {
+ helpers.clear(this.chart);
+ return this;
+ },
+
+ stop: function() {
+ // Stops any current animation loop occuring
+ Chart.animationService.cancelAnimation(this);
+ return this;
+ },
+
+ resize: function(silent) {
+ var me = this;
+ var chart = me.chart;
+ var canvas = chart.canvas;
+ var newWidth = helpers.getMaximumWidth(canvas);
+ var aspectRatio = chart.aspectRatio;
+ var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas);
+
+ var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;
+
+ if (!sizeChanged) {
+ return me;
+ }
+
+ canvas.width = chart.width = newWidth;
+ canvas.height = chart.height = newHeight;
+
+ helpers.retinaScale(chart);
+
+ // Notify any plugins about the resize
+ var newSize = {width: newWidth, height: newHeight};
+ Chart.plugins.notify('resize', [me, newSize]);
+
+ // Notify of resize
+ if (me.options.onResize) {
+ me.options.onResize(me, newSize);
+ }
+
+ if (!silent) {
+ me.stop();
+ me.update(me.options.responsiveAnimationDuration);
+ }
+
+ return me;
+ },
+
+ ensureScalesHaveIDs: function() {
+ var options = this.options;
+ var scalesOptions = options.scales || {};
+ var scaleOptions = options.scale;
+
+ helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
+ xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
+ });
+
+ helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
+ yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
+ });
+
+ if (scaleOptions) {
+ scaleOptions.id = scaleOptions.id || 'scale';
+ }
+ },
+
+ /**
+ * Builds a map of scale ID to scale object for future lookup.
+ */
+ buildScales: function() {
+ var me = this;
+ var options = me.options;
+ var scales = me.scales = {};
+ var items = [];
+
+ if (options.scales) {
+ items = items.concat(
+ (options.scales.xAxes || []).map(function(xAxisOptions) {
+ return {options: xAxisOptions, dtype: 'category'};
+ }),
+ (options.scales.yAxes || []).map(function(yAxisOptions) {
+ return {options: yAxisOptions, dtype: 'linear'};
+ })
+ );
+ }
+
+ if (options.scale) {
+ items.push({options: options.scale, dtype: 'radialLinear', isDefault: true});
+ }
+
+ helpers.each(items, function(item) {
+ var scaleOptions = item.options;
+ var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
+ var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
+ if (!scaleClass) {
+ return;
+ }
+
+ var scale = new scaleClass({
+ id: scaleOptions.id,
+ options: scaleOptions,
+ ctx: me.chart.ctx,
+ chart: me
+ });
+
+ scales[scale.id] = scale;
+
+ // TODO(SB): I think we should be able to remove this custom case (options.scale)
+ // and consider it as a regular scale part of the "scales"" map only! This would
+ // make the logic easier and remove some useless? custom code.
+ if (item.isDefault) {
+ me.scale = scale;
+ }
+ });
+
+ Chart.scaleService.addScalesToLayout(this);
+ },
+
+ updateLayout: function() {
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
+ },
+
+ buildOrUpdateControllers: function() {
+ var me = this;
+ var types = [];
+ var newControllers = [];
+
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ var meta = me.getDatasetMeta(datasetIndex);
+ if (!meta.type) {
+ meta.type = dataset.type || me.config.type;
+ }
+
+ types.push(meta.type);
+
+ if (meta.controller) {
+ meta.controller.updateIndex(datasetIndex);
+ } else {
+ meta.controller = new Chart.controllers[meta.type](me, datasetIndex);
+ newControllers.push(meta.controller);
+ }
+ }, me);
+
+ if (types.length > 1) {
+ for (var i = 1; i < types.length; i++) {
+ if (types[i] !== types[i - 1]) {
+ me.isCombo = true;
+ break;
+ }
+ }
+ }
+
+ return newControllers;
+ },
+
+ resetElements: function() {
+ var me = this;
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.reset();
+ }, me);
+ },
+
+ update: function(animationDuration, lazy) {
+ var me = this;
+ Chart.plugins.notify('beforeUpdate', [me]);
+
+ // In case the entire data object changed
+ me.tooltip._data = me.data;
+
+ // Make sure dataset controllers are updated and new controllers are reset
+ var newControllers = me.buildOrUpdateControllers();
+
+ // Make sure all dataset controllers have correct meta data counts
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
+ }, me);
+
+ Chart.layoutService.update(me, me.chart.width, me.chart.height);
+
+ // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
+ Chart.plugins.notify('afterScaleUpdate', [me]);
+
+ // Can only reset the new controllers after the scales have been updated
+ helpers.each(newControllers, function(controller) {
+ controller.reset();
+ });
+
+ me.updateDatasets();
+
+ // Do this before render so that any plugins that need final scale updates can use it
+ Chart.plugins.notify('afterUpdate', [me]);
+
+ me.render(animationDuration, lazy);
+ },
+
+ /**
+ * @method beforeDatasetsUpdate
+ * @description Called before all datasets are updated. If a plugin returns false,
+ * the datasets update will be cancelled until another chart update is triggered.
+ * @param {Object} instance the chart instance being updated.
+ * @returns {Boolean} false to cancel the datasets update.
+ * @memberof Chart.PluginBase
+ * @since version 2.1.5
+ * @instance
+ */
+
+ /**
+ * @method afterDatasetsUpdate
+ * @description Called after all datasets have been updated. Note that this
+ * extension will not be called if the datasets update has been cancelled.
+ * @param {Object} instance the chart instance being updated.
+ * @memberof Chart.PluginBase
+ * @since version 2.1.5
+ * @instance
+ */
+
+ /**
+ * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
+ * extension, in which case no datasets will be updated and the afterDatasetsUpdate
+ * notification will be skipped.
+ * @protected
+ * @instance
+ */
+ updateDatasets: function() {
+ var me = this;
+ var i, ilen;
+
+ if (Chart.plugins.notify('beforeDatasetsUpdate', [me])) {
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
+ me.getDatasetMeta(i).controller.update();
+ }
+
+ Chart.plugins.notify('afterDatasetsUpdate', [me]);
+ }
+ },
+
+ render: function(duration, lazy) {
+ var me = this;
+ Chart.plugins.notify('beforeRender', [me]);
+
+ var animationOptions = me.options.animation;
+ if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
+ var animation = new Chart.Animation();
+ animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps
+ animation.easing = animationOptions.easing;
+
+ // render function
+ animation.render = function(chartInstance, animationObject) {
+ var easingFunction = helpers.easingEffects[animationObject.easing];
+ var stepDecimal = animationObject.currentStep / animationObject.numSteps;
+ var easeDecimal = easingFunction(stepDecimal);
+
+ chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
+ };
+
+ // user events
+ animation.onAnimationProgress = animationOptions.onProgress;
+ animation.onAnimationComplete = animationOptions.onComplete;
+
+ Chart.animationService.addAnimation(me, animation, duration, lazy);
+ } else {
+ me.draw();
+ if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
+ animationOptions.onComplete.call(me);
+ }
+ }
+ return me;
+ },
+
+ draw: function(ease) {
+ var me = this;
+ var easingDecimal = ease || 1;
+ me.clear();
+
+ Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
+
+ // Draw all the scales
+ helpers.each(me.boxes, function(box) {
+ box.draw(me.chartArea);
+ }, me);
+ if (me.scale) {
+ me.scale.draw();
+ }
+
+ Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
+
+ // Draw each dataset via its respective controller (reversed to support proper line stacking)
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ if (me.isDatasetVisible(datasetIndex)) {
+ me.getDatasetMeta(datasetIndex).controller.draw(ease);
+ }
+ }, me, true);
+
+ Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
+
+ // Finally draw the tooltip
+ me.tooltip.transition(easingDecimal).draw();
+
+ Chart.plugins.notify('afterDraw', [me, easingDecimal]);
+ },
+
+ // Get the single element that was clicked on
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
+ getElementAtEvent: function(e) {
+ var me = this;
+ var eventPosition = helpers.getRelativePosition(e, me.chart);
+ var elementsArray = [];
+
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ if (me.isDatasetVisible(datasetIndex)) {
+ var meta = me.getDatasetMeta(datasetIndex);
+ helpers.each(meta.data, function(element) {
+ if (element.inRange(eventPosition.x, eventPosition.y)) {
+ elementsArray.push(element);
+ return elementsArray;
+ }
+ });
+ }
+ });
+
+ return elementsArray.slice(0, 1);
+ },
+
+ getElementsAtEvent: function(e) {
+ var me = this;
+ var eventPosition = helpers.getRelativePosition(e, me.chart);
+ var elementsArray = [];
+
+ var found = function() {
+ if (me.data.datasets) {
+ for (var i = 0; i < me.data.datasets.length; i++) {
+ var meta = me.getDatasetMeta(i);
+ if (me.isDatasetVisible(i)) {
+ for (var j = 0; j < meta.data.length; j++) {
+ if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
+ return meta.data[j];
+ }
+ }
+ }
+ }
+ }
+ }.call(me);
+
+ if (!found) {
+ return elementsArray;
+ }
+
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ if (me.isDatasetVisible(datasetIndex)) {
+ var meta = me.getDatasetMeta(datasetIndex),
+ element = meta.data[found._index];
+ if (element && !element._view.skip) {
+ elementsArray.push(element);
+ }
+ }
+ }, me);
+
+ return elementsArray;
+ },
+
+ getElementsAtXAxis: function(e) {
+ var me = this;
+ var eventPosition = helpers.getRelativePosition(e, me.chart);
+ var elementsArray = [];
+
+ var found = function() {
+ if (me.data.datasets) {
+ for (var i = 0; i < me.data.datasets.length; i++) {
+ var meta = me.getDatasetMeta(i);
+ if (me.isDatasetVisible(i)) {
+ for (var j = 0; j < meta.data.length; j++) {
+ if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) {
+ return meta.data[j];
+ }
+ }
+ }
+ }
+ }
+ }.call(me);
+
+ if (!found) {
+ return elementsArray;
+ }
+
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ if (me.isDatasetVisible(datasetIndex)) {
+ var meta = me.getDatasetMeta(datasetIndex);
+ var index = helpers.findIndex(meta.data, function(it) {
+ return found._model.x === it._model.x;
+ });
+ if (index !== -1 && !meta.data[index]._view.skip) {
+ elementsArray.push(meta.data[index]);
+ }
+ }
+ }, me);
+
+ return elementsArray;
+ },
+
+ getElementsAtEventForMode: function(e, mode) {
+ var me = this;
+ switch (mode) {
+ case 'single':
+ return me.getElementAtEvent(e);
+ case 'label':
+ return me.getElementsAtEvent(e);
+ case 'dataset':
+ return me.getDatasetAtEvent(e);
+ case 'x-axis':
+ return me.getElementsAtXAxis(e);
+ default:
+ return e;
+ }
+ },
+
+ getDatasetAtEvent: function(e) {
+ var elementsArray = this.getElementAtEvent(e);
+
+ if (elementsArray.length > 0) {
+ elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
+ }
+
+ return elementsArray;
+ },
+
+ getDatasetMeta: function(datasetIndex) {
+ var me = this;
+ var dataset = me.data.datasets[datasetIndex];
+ if (!dataset._meta) {
+ dataset._meta = {};
+ }
+
+ var meta = dataset._meta[me.id];
+ if (!meta) {
+ meta = dataset._meta[me.id] = {
+ type: null,
+ data: [],
+ dataset: null,
+ controller: null,
+ hidden: null, // See isDatasetVisible() comment
+ xAxisID: null,
+ yAxisID: null
+ };
+ }
+
+ return meta;
+ },
+
+ getVisibleDatasetCount: function() {
+ var count = 0;
+ for (var i = 0, ilen = this.data.datasets.length; i numMetaData) {
+ // Add new elements
+ for (var index = numMetaData; index < numData; ++index) {
+ this.addElementAndReset(index);
+ }
+ }
+ },
+
+ update: noop,
+
+ draw: function(ease) {
+ var easingDecimal = ease || 1;
+ helpers.each(this.getMeta().data, function(element) {
+ element.transition(easingDecimal).draw();
+ });
+ },
+
+ removeHoverStyle: function(element, elementOpts) {
+ var dataset = this.chart.data.datasets[element._datasetIndex],
+ index = element._index,
+ custom = element.custom || {},
+ valueOrDefault = helpers.getValueAtIndexOrDefault,
+ model = element._model;
+
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
+ },
+
+ setHoverStyle: function(element) {
+ var dataset = this.chart.data.datasets[element._datasetIndex],
+ index = element._index,
+ custom = element.custom || {},
+ valueOrDefault = helpers.getValueAtIndexOrDefault,
+ getHoverColor = helpers.getHoverColor,
+ model = element._model;
+
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
+ }
+
+ });
+
+ Chart.DatasetController.extend = helpers.inherits;
+};
+
+},{}],25:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.elements = {};
+
+ Chart.Element = function(configuration) {
+ helpers.extend(this, configuration);
+ this.initialize.apply(this, arguments);
+ };
+
+ helpers.extend(Chart.Element.prototype, {
+
+ initialize: function() {
+ this.hidden = false;
+ },
+
+ pivot: function() {
+ var me = this;
+ if (!me._view) {
+ me._view = helpers.clone(me._model);
+ }
+ me._start = helpers.clone(me._view);
+ return me;
+ },
+
+ transition: function(ease) {
+ var me = this;
+
+ if (!me._view) {
+ me._view = helpers.clone(me._model);
+ }
+
+ // No animation -> No Transition
+ if (ease === 1) {
+ me._view = me._model;
+ me._start = null;
+ return me;
+ }
+
+ if (!me._start) {
+ me.pivot();
+ }
+
+ helpers.each(me._model, function(value, key) {
+
+ if (key[0] === '_') {
+ // Only non-underscored properties
+ // Init if doesn't exist
+ } else if (!me._view.hasOwnProperty(key)) {
+ if (typeof value === 'number' && !isNaN(me._view[key])) {
+ me._view[key] = value * ease;
+ } else {
+ me._view[key] = value;
+ }
+ // No unnecessary computations
+ } else if (value === me._view[key]) {
+ // It's the same! Woohoo!
+ // Color transitions if possible
+ } else if (typeof value === 'string') {
+ try {
+ var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease);
+ me._view[key] = color.rgbString();
+ } catch (err) {
+ me._view[key] = value;
+ }
+ // Number transitions
+ } else if (typeof value === 'number') {
+ var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0;
+ me._view[key] = ((me._model[key] - startVal) * ease) + startVal;
+ // Everything else
+ } else {
+ me._view[key] = value;
+ }
+ }, me);
+
+ return me;
+ },
+
+ tooltipPosition: function() {
+ return {
+ x: this._model.x,
+ y: this._model.y
+ };
+ },
+
+ hasValue: function() {
+ return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
+ }
+ });
+
+ Chart.Element.extend = helpers.inherits;
+
+};
+
+},{}],26:[function(require,module,exports){
+/* global window: false */
+/* global document: false */
+'use strict';
+
+var color = require(3);
+
+module.exports = function(Chart) {
+ // Global Chart helpers object for utility methods and classes
+ var helpers = Chart.helpers = {};
+
+ // -- Basic js utility methods
+ helpers.each = function(loopable, callback, self, reverse) {
+ // Check to see if null or undefined firstly.
+ var i, len;
+ if (helpers.isArray(loopable)) {
+ len = loopable.length;
+ if (reverse) {
+ for (i = len - 1; i >= 0; i--) {
+ callback.call(self, loopable[i], i);
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ callback.call(self, loopable[i], i);
+ }
+ }
+ } else if (typeof loopable === 'object') {
+ var keys = Object.keys(loopable);
+ len = keys.length;
+ for (i = 0; i < len; i++) {
+ callback.call(self, loopable[keys[i]], keys[i]);
+ }
+ }
+ };
+ helpers.clone = function(obj) {
+ var objClone = {};
+ helpers.each(obj, function(value, key) {
+ if (helpers.isArray(value)) {
+ objClone[key] = value.slice(0);
+ } else if (typeof value === 'object' && value !== null) {
+ objClone[key] = helpers.clone(value);
+ } else {
+ objClone[key] = value;
+ }
+ });
+ return objClone;
+ };
+ helpers.extend = function(base) {
+ var setFn = function(value, key) {
+ base[key] = value;
+ };
+ for (var i = 1, ilen = arguments.length; i < ilen; i++) {
+ helpers.each(arguments[i], setFn);
+ }
+ return base;
+ };
+ // Need a special merge function to chart configs since they are now grouped
+ helpers.configMerge = function(_base) {
+ var base = helpers.clone(_base);
+ helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
+ helpers.each(extension, function(value, key) {
+ if (key === 'scales') {
+ // Scale config merging is complex. Add out own function here for that
+ base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
+
+ } else if (key === 'scale') {
+ // Used in polar area & radar charts since there is only one scale
+ base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
+ } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
+ // In this case we have an array of objects replacing another array. Rather than doing a strict replace,
+ // merge. This allows easy scale option merging
+ var baseArray = base[key];
+
+ helpers.each(value, function(valueObj, index) {
+
+ if (index < baseArray.length) {
+ if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) {
+ // Two objects are coming together. Do a merge of them.
+ baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
+ } else {
+ // Just overwrite in this case since there is nothing to merge
+ baseArray[index] = valueObj;
+ }
+ } else {
+ baseArray.push(valueObj); // nothing to merge
+ }
+ });
+
+ } else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {
+ // If we are overwriting an object with an object, do a merge of the properties.
+ base[key] = helpers.configMerge(base[key], value);
+
+ } else {
+ // can just overwrite the value in this case
+ base[key] = value;
+ }
+ });
+ });
+
+ return base;
+ };
+ helpers.scaleMerge = function(_base, extension) {
+ var base = helpers.clone(_base);
+
+ helpers.each(extension, function(value, key) {
+ if (key === 'xAxes' || key === 'yAxes') {
+ // These properties are arrays of items
+ if (base.hasOwnProperty(key)) {
+ helpers.each(value, function(valueObj, index) {
+ var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
+ var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
+ if (index >= base[key].length || !base[key][index].type) {
+ base[key].push(helpers.configMerge(axisDefaults, valueObj));
+ } else if (valueObj.type && valueObj.type !== base[key][index].type) {
+ // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
+ base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);
+ } else {
+ // Type is the same
+ base[key][index] = helpers.configMerge(base[key][index], valueObj);
+ }
+ });
+ } else {
+ base[key] = [];
+ helpers.each(value, function(valueObj) {
+ var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
+ base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
+ });
+ }
+ } else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {
+ // If we are overwriting an object with an object, do a merge of the properties.
+ base[key] = helpers.configMerge(base[key], value);
+
+ } else {
+ // can just overwrite the value in this case
+ base[key] = value;
+ }
+ });
+
+ return base;
+ };
+ helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
+ if (value === undefined || value === null) {
+ return defaultValue;
+ }
+
+ if (helpers.isArray(value)) {
+ return index < value.length ? value[index] : defaultValue;
+ }
+
+ return value;
+ };
+ helpers.getValueOrDefault = function(value, defaultValue) {
+ return value === undefined ? defaultValue : value;
+ };
+ helpers.indexOf = Array.prototype.indexOf?
+ function(array, item) {
+ return array.indexOf(item);
+ }:
+ function(array, item) {
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ helpers.where = function(collection, filterCallback) {
+ if (helpers.isArray(collection) && Array.prototype.filter) {
+ return collection.filter(filterCallback);
+ }
+ var filtered = [];
+
+ helpers.each(collection, function(item) {
+ if (filterCallback(item)) {
+ filtered.push(item);
+ }
+ });
+
+ return filtered;
+ };
+ helpers.findIndex = Array.prototype.findIndex?
+ function(array, callback, scope) {
+ return array.findIndex(callback, scope);
+ } :
+ function(array, callback, scope) {
+ scope = scope === undefined? array : scope;
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
+ if (callback.call(scope, array[i], i, array)) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
+ // Default to start of the array
+ if (startIndex === undefined || startIndex === null) {
+ startIndex = -1;
+ }
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ };
+ helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
+ // Default to end of the array
+ if (startIndex === undefined || startIndex === null) {
+ startIndex = arrayToSearch.length;
+ }
+ for (var i = startIndex - 1; i >= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ };
+ helpers.inherits = function(extensions) {
+ // Basic javascript inheritance based on the model created in Backbone.js
+ var me = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
+ return me.apply(this, arguments);
+ };
+
+ var Surrogate = function() {
+ this.constructor = ChartElement;
+ };
+ Surrogate.prototype = me.prototype;
+ ChartElement.prototype = new Surrogate();
+
+ ChartElement.extend = helpers.inherits;
+
+ if (extensions) {
+ helpers.extend(ChartElement.prototype, extensions);
+ }
+
+ ChartElement.__super__ = me.prototype;
+
+ return ChartElement;
+ };
+ helpers.noop = function() {};
+ helpers.uid = (function() {
+ var id = 0;
+ return function() {
+ return id++;
+ };
+ }());
+ // -- Math methods
+ helpers.isNumber = function(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ };
+ helpers.almostEquals = function(x, y, epsilon) {
+ return Math.abs(x - y) < epsilon;
+ };
+ helpers.max = function(array) {
+ return array.reduce(function(max, value) {
+ if (!isNaN(value)) {
+ return Math.max(max, value);
+ }
+ return max;
+ }, Number.NEGATIVE_INFINITY);
+ };
+ helpers.min = function(array) {
+ return array.reduce(function(min, value) {
+ if (!isNaN(value)) {
+ return Math.min(min, value);
+ }
+ return min;
+ }, Number.POSITIVE_INFINITY);
+ };
+ helpers.sign = Math.sign?
+ function(x) {
+ return Math.sign(x);
+ } :
+ function(x) {
+ x = +x; // convert to a number
+ if (x === 0 || isNaN(x)) {
+ return x;
+ }
+ return x > 0 ? 1 : -1;
+ };
+ helpers.log10 = Math.log10?
+ function(x) {
+ return Math.log10(x);
+ } :
+ function(x) {
+ return Math.log(x) / Math.LN10;
+ };
+ helpers.toRadians = function(degrees) {
+ return degrees * (Math.PI / 180);
+ };
+ helpers.toDegrees = function(radians) {
+ return radians * (180 / Math.PI);
+ };
+ // Gets the angle from vertical upright to the point about a centre.
+ helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
+ radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ if (angle < (-0.5 * Math.PI)) {
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ };
+ helpers.aliasPixel = function(pixelWidth) {
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ };
+ helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
+ // Props to Rob Spencer at scaled innovation for his post on splining between points
+ // http://scaledinnovation.com/analytics/splines/aboutSplines.html
+
+ // This function must also respect "skipped" points
+
+ var previous = firstPoint.skip ? middlePoint : firstPoint,
+ current = middlePoint,
+ next = afterPoint.skip ? middlePoint : afterPoint;
+
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
+
+ var s01 = d01 / (d01 + d12);
+ var s12 = d12 / (d01 + d12);
+
+ // If all points are the same, s01 & s02 will be inf
+ s01 = isNaN(s01) ? 0 : s01;
+ s12 = isNaN(s12) ? 0 : s12;
+
+ var fa = t * s01; // scaling factor for triangle Ta
+ var fb = t * s12;
+
+ return {
+ previous: {
+ x: current.x - fa * (next.x - previous.x),
+ y: current.y - fa * (next.y - previous.y)
+ },
+ next: {
+ x: current.x + fb * (next.x - previous.x),
+ y: current.y + fb * (next.y - previous.y)
+ }
+ };
+ };
+ helpers.EPSILON = Number.EPSILON || 1e-14;
+ helpers.splineCurveMonotone = function(points) {
+ // This function calculates Bézier control points in a similar way than |splineCurve|,
+ // but preserves monotonicity of the provided data and ensures no local extremums are added
+ // between the dataset discrete points due to the interpolation.
+ // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
+
+ var pointsWithTangents = (points || []).map(function(point) {
+ return {
+ model: point._model,
+ deltaK: 0,
+ mK: 0
+ };
+ });
+
+ // Calculate slopes (deltaK) and initialize tangents (mK)
+ var pointsLen = pointsWithTangents.length;
+ var i, pointBefore, pointCurrent, pointAfter;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointAfter && !pointAfter.model.skip) {
+ pointCurrent.deltaK = (pointAfter.model.y - pointCurrent.model.y) / (pointAfter.model.x - pointCurrent.model.x);
+ }
+
+ if (!pointBefore || pointBefore.model.skip) {
+ pointCurrent.mK = pointCurrent.deltaK;
+ } else if (!pointAfter || pointAfter.model.skip) {
+ pointCurrent.mK = pointBefore.deltaK;
+ } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
+ pointCurrent.mK = 0;
+ } else {
+ pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
+ }
+ }
+
+ // Adjust tangents to ensure monotonic properties
+ var alphaK, betaK, tauK, squaredMagnitude;
+ for (i = 0; i < pointsLen - 1; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ pointAfter = pointsWithTangents[i + 1];
+ if (pointCurrent.model.skip || pointAfter.model.skip) {
+ continue;
+ }
+
+ if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
+ pointCurrent.mK = pointAfter.mK = 0;
+ continue;
+ }
+
+ alphaK = pointCurrent.mK / pointCurrent.deltaK;
+ betaK = pointAfter.mK / pointCurrent.deltaK;
+ squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
+ if (squaredMagnitude <= 9) {
+ continue;
+ }
+
+ tauK = 3 / Math.sqrt(squaredMagnitude);
+ pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
+ pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
+ }
+
+ // Compute control points
+ var deltaX;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointBefore && !pointBefore.model.skip) {
+ deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
+ pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
+ pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
+ }
+ if (pointAfter && !pointAfter.model.skip) {
+ deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
+ pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
+ pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
+ }
+ }
+ };
+ helpers.nextItem = function(collection, index, loop) {
+ if (loop) {
+ return index >= collection.length - 1 ? collection[0] : collection[index + 1];
+ }
+ return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
+ };
+ helpers.previousItem = function(collection, index, loop) {
+ if (loop) {
+ return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
+ }
+ return index <= 0 ? collection[0] : collection[index - 1];
+ };
+ // Implementation of the nice number algorithm used in determining where axis labels will go
+ helpers.niceNum = function(range, round) {
+ var exponent = Math.floor(helpers.log10(range));
+ var fraction = range / Math.pow(10, exponent);
+ var niceFraction;
+
+ if (round) {
+ if (fraction < 1.5) {
+ niceFraction = 1;
+ } else if (fraction < 3) {
+ niceFraction = 2;
+ } else if (fraction < 7) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+ } else if (fraction <= 1.0) {
+ niceFraction = 1;
+ } else if (fraction <= 2) {
+ niceFraction = 2;
+ } else if (fraction <= 5) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+
+ return niceFraction * Math.pow(10, exponent);
+ };
+ // Easing functions adapted from Robert Penner's easing equations
+ // http://www.robertpenner.com/easing/
+ var easingEffects = helpers.easingEffects = {
+ linear: function(t) {
+ return t;
+ },
+ easeInQuad: function(t) {
+ return t * t;
+ },
+ easeOutQuad: function(t) {
+ return -1 * t * (t - 2);
+ },
+ easeInOutQuad: function(t) {
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * t * t;
+ }
+ return -1 / 2 * ((--t) * (t - 2) - 1);
+ },
+ easeInCubic: function(t) {
+ return t * t * t;
+ },
+ easeOutCubic: function(t) {
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
+ },
+ easeInOutCubic: function(t) {
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * t * t * t;
+ }
+ return 1 / 2 * ((t -= 2) * t * t + 2);
+ },
+ easeInQuart: function(t) {
+ return t * t * t * t;
+ },
+ easeOutQuart: function(t) {
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+ },
+ easeInOutQuart: function(t) {
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * t * t * t * t;
+ }
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
+ },
+ easeInQuint: function(t) {
+ return 1 * (t /= 1) * t * t * t * t;
+ },
+ easeOutQuint: function(t) {
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+ },
+ easeInOutQuint: function(t) {
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * t * t * t * t * t;
+ }
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+ },
+ easeInSine: function(t) {
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+ },
+ easeOutSine: function(t) {
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
+ },
+ easeInOutSine: function(t) {
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+ },
+ easeInExpo: function(t) {
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+ },
+ easeOutExpo: function(t) {
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+ },
+ easeInOutExpo: function(t) {
+ if (t === 0) {
+ return 0;
+ }
+ if (t === 1) {
+ return 1;
+ }
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * Math.pow(2, 10 * (t - 1));
+ }
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+ },
+ easeInCirc: function(t) {
+ if (t >= 1) {
+ return t;
+ }
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+ },
+ easeOutCirc: function(t) {
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+ },
+ easeInOutCirc: function(t) {
+ if ((t /= 1 / 2) < 1) {
+ return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+ }
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ easeInElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if ((t /= 1) === 1) {
+ return 1;
+ }
+ if (!p) {
+ p = 1 * 0.3;
+ }
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ },
+ easeOutElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if ((t /= 1) === 1) {
+ return 1;
+ }
+ if (!p) {
+ p = 1 * 0.3;
+ }
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+ },
+ easeInOutElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if ((t /= 1 / 2) === 2) {
+ return 1;
+ }
+ if (!p) {
+ p = 1 * (0.3 * 1.5);
+ }
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ if (t < 1) {
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ }
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function(t) {
+ var s = 1.70158;
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
+ },
+ easeOutBack: function(t) {
+ var s = 1.70158;
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+ },
+ easeInOutBack: function(t) {
+ var s = 1.70158;
+ if ((t /= 1 / 2) < 1) {
+ return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ }
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+ easeInBounce: function(t) {
+ return 1 - easingEffects.easeOutBounce(1 - t);
+ },
+ easeOutBounce: function(t) {
+ if ((t /= 1) < (1 / 2.75)) {
+ return 1 * (7.5625 * t * t);
+ } else if (t < (2 / 2.75)) {
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+ } else if (t < (2.5 / 2.75)) {
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+ }
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+ },
+ easeInOutBounce: function(t) {
+ if (t < 1 / 2) {
+ return easingEffects.easeInBounce(t * 2) * 0.5;
+ }
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+ }
+ };
+ // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ helpers.requestAnimFrame = (function() {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ }());
+ helpers.cancelAnimFrame = (function() {
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ window.msCancelAnimationFrame ||
+ function(callback) {
+ return window.clearTimeout(callback, 1000 / 60);
+ };
+ }());
+ // -- DOM methods
+ helpers.getRelativePosition = function(evt, chart) {
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt,
+ canvas = evt.currentTarget || evt.srcElement,
+ boundingRect = canvas.getBoundingClientRect();
+
+ var touches = e.touches;
+ if (touches && touches.length > 0) {
+ mouseX = touches[0].clientX;
+ mouseY = touches[0].clientY;
+
+ } else {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ }
+
+ // Scale mouse coordinates into canvas coordinates
+ // by following the pattern laid out by 'jerryj' in the comments of
+ // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
+ var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
+ var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
+ var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
+ var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
+
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
+
+ return {
+ x: mouseX,
+ y: mouseY
+ };
+
+ };
+ helpers.addEvent = function(node, eventType, method) {
+ if (node.addEventListener) {
+ node.addEventListener(eventType, method);
+ } else if (node.attachEvent) {
+ node.attachEvent('on' + eventType, method);
+ } else {
+ node['on' + eventType] = method;
+ }
+ };
+ helpers.removeEvent = function(node, eventType, handler) {
+ if (node.removeEventListener) {
+ node.removeEventListener(eventType, handler, false);
+ } else if (node.detachEvent) {
+ node.detachEvent('on' + eventType, handler);
+ } else {
+ node['on' + eventType] = helpers.noop;
+ }
+ };
+ helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
+ // Create the events object if it's not already present
+ var events = chartInstance.events = chartInstance.events || {};
+
+ helpers.each(arrayOfEvents, function(eventName) {
+ events[eventName] = function() {
+ handler.apply(chartInstance, arguments);
+ };
+ helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]);
+ });
+ };
+ helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
+ var canvas = chartInstance.chart.canvas;
+ helpers.each(arrayOfEvents, function(handler, eventName) {
+ helpers.removeEvent(canvas, eventName, handler);
+ });
+ };
+
+ // Private helper function to convert max-width/max-height values that may be percentages into a number
+ function parseMaxStyle(styleValue, node, parentProperty) {
+ var valueInPixels;
+ if (typeof(styleValue) === 'string') {
+ valueInPixels = parseInt(styleValue, 10);
+
+ if (styleValue.indexOf('%') !== -1) {
+ // percentage * size in dimension
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
+ }
+ } else {
+ valueInPixels = styleValue;
+ }
+
+ return valueInPixels;
+ }
+
+ /**
+ * Returns if the given value contains an effective constraint.
+ * @private
+ */
+ function isConstrainedValue(value) {
+ return value !== undefined && value !== null && value !== 'none';
+ }
+
+ // Private helper to get a constraint dimension
+ // @param domNode : the node to check the constraint on
+ // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
+ // @param percentageProperty : property of parent to use when calculating width as a percentage
+ // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
+ function getConstraintDimension(domNode, maxStyle, percentageProperty) {
+ var view = document.defaultView;
+ var parentNode = domNode.parentNode;
+ var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
+ var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
+ var hasCNode = isConstrainedValue(constrainedNode);
+ var hasCContainer = isConstrainedValue(constrainedContainer);
+ var infinity = Number.POSITIVE_INFINITY;
+
+ if (hasCNode || hasCContainer) {
+ return Math.min(
+ hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
+ hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
+ }
+
+ return 'none';
+ }
+ // returns Number or undefined if no constraint
+ helpers.getConstraintWidth = function(domNode) {
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
+ };
+ // returns Number or undefined if no constraint
+ helpers.getConstraintHeight = function(domNode) {
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
+ };
+ helpers.getMaximumWidth = function(domNode) {
+ var container = domNode.parentNode;
+ var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
+ var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
+ var w = container.clientWidth - paddingLeft - paddingRight;
+ var cw = helpers.getConstraintWidth(domNode);
+ return isNaN(cw)? w : Math.min(w, cw);
+ };
+ helpers.getMaximumHeight = function(domNode) {
+ var container = domNode.parentNode;
+ var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
+ var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
+ var h = container.clientHeight - paddingTop - paddingBottom;
+ var ch = helpers.getConstraintHeight(domNode);
+ return isNaN(ch)? h : Math.min(h, ch);
+ };
+ helpers.getStyle = function(el, property) {
+ return el.currentStyle ?
+ el.currentStyle[property] :
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
+ };
+ helpers.retinaScale = function(chart) {
+ var ctx = chart.ctx;
+ var canvas = chart.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+ var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
+
+ if (pixelRatio !== 1) {
+ canvas.height = height * pixelRatio;
+ canvas.width = width * pixelRatio;
+ ctx.scale(pixelRatio, pixelRatio);
+
+ // Store the device pixel ratio so that we can go backwards in `destroy`.
+ // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
+ // when destroy is called
+ chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
+ }
+
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ };
+ // -- Canvas methods
+ helpers.clear = function(chart) {
+ chart.ctx.clearRect(0, 0, chart.width, chart.height);
+ };
+ helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
+ return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
+ };
+ helpers.longestText = function(ctx, font, arrayOfThings, cache) {
+ cache = cache || {};
+ var data = cache.data = cache.data || {};
+ var gc = cache.garbageCollect = cache.garbageCollect || [];
+
+ if (cache.font !== font) {
+ data = cache.data = {};
+ gc = cache.garbageCollect = [];
+ cache.font = font;
+ }
+
+ ctx.font = font;
+ var longest = 0;
+ helpers.each(arrayOfThings, function(thing) {
+ // Undefined strings and arrays should not be measured
+ if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
+ longest = helpers.measureText(ctx, data, gc, longest, thing);
+ } else if (helpers.isArray(thing)) {
+ // if it is an array lets measure each element
+ // to do maybe simplify this function a bit so we can do this more recursively?
+ helpers.each(thing, function(nestedThing) {
+ // Undefined strings and arrays should not be measured
+ if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
+ longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
+ }
+ });
+ }
+ });
+
+ var gcLen = gc.length / 2;
+ if (gcLen > arrayOfThings.length) {
+ for (var i = 0; i < gcLen; i++) {
+ delete data[gc[i]];
+ }
+ gc.splice(0, gcLen);
+ }
+ return longest;
+ };
+ helpers.measureText = function(ctx, data, gc, longest, string) {
+ var textWidth = data[string];
+ if (!textWidth) {
+ textWidth = data[string] = ctx.measureText(string).width;
+ gc.push(string);
+ }
+ if (textWidth > longest) {
+ longest = textWidth;
+ }
+ return longest;
+ };
+ helpers.numberOfLabelLines = function(arrayOfThings) {
+ var numberOfLines = 1;
+ helpers.each(arrayOfThings, function(thing) {
+ if (helpers.isArray(thing)) {
+ if (thing.length > numberOfLines) {
+ numberOfLines = thing.length;
+ }
+ }
+ });
+ return numberOfLines;
+ };
+ helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ };
+ helpers.color = function(c) {
+ if (!color) {
+ console.error('Color.js not found!');
+ return c;
+ }
+
+ /* global CanvasGradient */
+ if (c instanceof CanvasGradient) {
+ return color(Chart.defaults.global.defaultColor);
+ }
+
+ return color(c);
+ };
+ helpers.addResizeListener = function(node, callback) {
+ // Hide an iframe before the node
+ var hiddenIframe = document.createElement('iframe');
+ var hiddenIframeClass = 'chartjs-hidden-iframe';
+
+ if (hiddenIframe.classlist) {
+ // can use classlist
+ hiddenIframe.classlist.add(hiddenIframeClass);
+ } else {
+ hiddenIframe.setAttribute('class', hiddenIframeClass);
+ }
+
+ // Set the style
+ hiddenIframe.tabIndex = -1;
+ var style = hiddenIframe.style;
+ style.width = '100%';
+ style.display = 'block';
+ style.border = 0;
+ style.height = 0;
+ style.margin = 0;
+ style.position = 'absolute';
+ style.left = 0;
+ style.right = 0;
+ style.top = 0;
+ style.bottom = 0;
+
+ // Insert the iframe so that contentWindow is available
+ node.insertBefore(hiddenIframe, node.firstChild);
+
+ (hiddenIframe.contentWindow || hiddenIframe).onresize = function() {
+ if (callback) {
+ return callback();
+ }
+ };
+ };
+ helpers.removeResizeListener = function(node) {
+ var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
+
+ // Remove the resize detect iframe
+ if (hiddenIframe) {
+ hiddenIframe.parentNode.removeChild(hiddenIframe);
+ }
+ };
+ helpers.isArray = Array.isArray?
+ function(obj) {
+ return Array.isArray(obj);
+ } :
+ function(obj) {
+ return Object.prototype.toString.call(obj) === '[object Array]';
+ };
+ // ! @see http://stackoverflow.com/a/14853974
+ helpers.arrayEquals = function(a0, a1) {
+ var i, ilen, v0, v1;
+
+ if (!a0 || !a1 || a0.length !== a1.length) {
+ return false;
+ }
+
+ for (i = 0, ilen=a0.length; i < ilen; ++i) {
+ v0 = a0[i];
+ v1 = a1[i];
+
+ if (v0 instanceof Array && v1 instanceof Array) {
+ if (!helpers.arrayEquals(v0, v1)) {
+ return false;
+ }
+ } else if (v0 !== v1) {
+ // NOTE: two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+
+ return true;
+ };
+ helpers.callCallback = function(fn, args, _tArg) {
+ if (fn && typeof fn.call === 'function') {
+ fn.apply(_tArg, args);
+ }
+ };
+ helpers.getHoverColor = function(colorValue) {
+ /* global CanvasPattern */
+ return (colorValue instanceof CanvasPattern) ?
+ colorValue :
+ helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
+ };
+};
+
+},{"3":3}],27:[function(require,module,exports){
+'use strict';
+
+module.exports = function() {
+
+ // Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(context, config) {
+ var me = this;
+ var helpers = Chart.helpers;
+ me.config = config || {
+ data: {
+ datasets: []
+ }
+ };
+
+ // Support a jQuery'd canvas element
+ if (context.length && context[0].getContext) {
+ context = context[0];
+ }
+
+ // Support a canvas domnode
+ if (context.getContext) {
+ context = context.getContext('2d');
+ }
+
+ me.ctx = context;
+ me.canvas = context.canvas;
+
+ context.canvas.style.display = context.canvas.style.display || 'block';
+
+ // Figure out what the size of the chart will be.
+ // If the canvas has a specified width and height, we use those else
+ // we look to see if the canvas node has a CSS width and height.
+ // If there is still no height, fill the parent container
+ me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas);
+ me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas);
+
+ me.aspectRatio = me.width / me.height;
+
+ if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) {
+ // If the canvas has no size, try and figure out what the aspect ratio will be.
+ // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
+ // else use the canvas default ratio of 2
+ me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
+ }
+
+ // Store the original style of the element so we can set it back
+ me.originalCanvasStyleWidth = context.canvas.style.width;
+ me.originalCanvasStyleHeight = context.canvas.style.height;
+
+ // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+ helpers.retinaScale(me);
+ me.controller = new Chart.Controller(me);
+
+ // Always bind this so that if the responsive state changes we still work
+ helpers.addResizeListener(context.canvas.parentNode, function() {
+ if (me.controller && me.controller.config.options.responsive) {
+ me.controller.resize();
+ }
+ });
+
+ return me.controller ? me.controller : me;
+
+ };
+
+ // Globally expose the defaults to allow for user updating/changing
+ Chart.defaults = {
+ global: {
+ responsive: true,
+ responsiveAnimationDuration: 0,
+ maintainAspectRatio: true,
+ events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
+ hover: {
+ onHover: null,
+ mode: 'single',
+ animationDuration: 400
+ },
+ onClick: null,
+ defaultColor: 'rgba(0,0,0,0.1)',
+ defaultFontColor: '#666',
+ defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+ defaultFontSize: 12,
+ defaultFontStyle: 'normal',
+ showLines: true,
+
+ // Element defaults defined in element extensions
+ elements: {},
+
+ // Legend callback string
+ legendCallback: function(chart) {
+ var text = [];
+ text.push('');
+ for (var i = 0; i < chart.data.datasets.length; i++) {
+ text.push('- ');
+ if (chart.data.datasets[i].label) {
+ text.push(chart.data.datasets[i].label);
+ }
+ text.push('
');
+ }
+ text.push('
');
+
+ return text.join('');
+ }
+ }
+ };
+
+ Chart.Chart = Chart;
+
+ return Chart;
+
+};
+
+},{}],28:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ // The layout service is very self explanatory. It's responsible for the layout within a chart.
+ // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
+ // It is this service's responsibility of carrying out that layout.
+ Chart.layoutService = {
+ defaults: {},
+
+ // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
+ addBox: function(chartInstance, box) {
+ if (!chartInstance.boxes) {
+ chartInstance.boxes = [];
+ }
+ chartInstance.boxes.push(box);
+ },
+
+ removeBox: function(chartInstance, box) {
+ if (!chartInstance.boxes) {
+ return;
+ }
+ chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
+ },
+
+ // The most important function
+ update: function(chartInstance, width, height) {
+
+ if (!chartInstance) {
+ return;
+ }
+
+ var xPadding = 0;
+ var yPadding = 0;
+
+ var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position === 'left';
+ });
+ var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position === 'right';
+ });
+ var topBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position === 'top';
+ });
+ var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position === 'bottom';
+ });
+
+ // Boxes that overlay the chartarea such as the radialLinear scale
+ var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position === 'chartArea';
+ });
+
+ // Ensure that full width boxes are at the very top / bottom
+ topBoxes.sort(function(a, b) {
+ return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
+ });
+ bottomBoxes.sort(function(a, b) {
+ return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
+ });
+
+ // Essentially we now have any number of boxes on each of the 4 sides.
+ // Our canvas looks like the following.
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
+ // B1 is the bottom axis
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
+ // an error will be thrown.
+ //
+ // |----------------------------------------------------|
+ // | T1 (Full Width) |
+ // |----------------------------------------------------|
+ // | | | T2 | |
+ // | |----|-------------------------------------|----|
+ // | | | C1 | | C2 | |
+ // | | |----| |----| |
+ // | | | | |
+ // | L1 | L2 | ChartArea (C0) | R1 |
+ // | | | | |
+ // | | |----| |----| |
+ // | | | C3 | | C4 | |
+ // | |----|-------------------------------------|----|
+ // | | | B1 | |
+ // |----------------------------------------------------|
+ // | B2 (Full Width) |
+ // |----------------------------------------------------|
+ //
+ // What we do to find the best sizing, we do the following
+ // 1. Determine the minimum size of the chart area.
+ // 2. Split the remaining width equally between each vertical axis
+ // 3. Split the remaining height equally between each horizontal axis
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
+ // 6. Refit each axis
+ // 7. Position each axis in the final location
+ // 8. Tell the chart the final location of the chart area
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
+
+ // Step 1
+ var chartWidth = width - (2 * xPadding);
+ var chartHeight = height - (2 * yPadding);
+ var chartAreaWidth = chartWidth / 2; // min 50%
+ var chartAreaHeight = chartHeight / 2; // min 50%
+
+ // Step 2
+ var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
+
+ // Step 3
+ var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
+
+ // Step 4
+ var maxChartAreaWidth = chartWidth;
+ var maxChartAreaHeight = chartHeight;
+ var minBoxSizes = [];
+
+ function getMinimumBoxSize(box) {
+ var minSize;
+ var isHorizontal = box.isHorizontal();
+
+ if (isHorizontal) {
+ minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
+ maxChartAreaHeight -= minSize.height;
+ } else {
+ minSize = box.update(verticalBoxWidth, chartAreaHeight);
+ maxChartAreaWidth -= minSize.width;
+ }
+
+ minBoxSizes.push({
+ horizontal: isHorizontal,
+ minSize: minSize,
+ box: box
+ });
+ }
+
+ helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
+
+ // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
+ // be if the axes are drawn at their minimum sizes.
+
+ // Steps 5 & 6
+ var totalLeftBoxesWidth = xPadding;
+ var totalRightBoxesWidth = xPadding;
+ var totalTopBoxesHeight = yPadding;
+ var totalBottomBoxesHeight = yPadding;
+
+ // Function to fit a box
+ function fitBox(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
+ return minBox.box === box;
+ });
+
+ if (minBoxSize) {
+ if (box.isHorizontal()) {
+ var scaleMargin = {
+ left: totalLeftBoxesWidth,
+ right: totalRightBoxesWidth,
+ top: 0,
+ bottom: 0
+ };
+
+ // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
+ // on the margin. Sometimes they need to increase in size slightly
+ box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
+ } else {
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight);
+ }
+ }
+ }
+
+ // Update, and calculate the left and right margins for the horizontal boxes
+ helpers.each(leftBoxes.concat(rightBoxes), fitBox);
+
+ helpers.each(leftBoxes, function(box) {
+ totalLeftBoxesWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightBoxesWidth += box.width;
+ });
+
+ // Set the Left and Right margins for the horizontal boxes
+ helpers.each(topBoxes.concat(bottomBoxes), fitBox);
+
+ // Figure out how much margin is on the top and bottom of the vertical boxes
+ helpers.each(topBoxes, function(box) {
+ totalTopBoxesHeight += box.height;
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomBoxesHeight += box.height;
+ });
+
+ function finalFitVerticalBox(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
+ return minSize.box === box;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopBoxesHeight,
+ bottom: totalBottomBoxesHeight
+ };
+
+ if (minBoxSize) {
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
+ }
+ }
+
+ // Let the left layout know the final margin
+ helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
+
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
+ totalLeftBoxesWidth = xPadding;
+ totalRightBoxesWidth = xPadding;
+ totalTopBoxesHeight = yPadding;
+ totalBottomBoxesHeight = yPadding;
+
+ helpers.each(leftBoxes, function(box) {
+ totalLeftBoxesWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightBoxesWidth += box.width;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ totalTopBoxesHeight += box.height;
+ });
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomBoxesHeight += box.height;
+ });
+
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
+ // without calling `fit` again
+ var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
+ var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
+
+ if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
+ helpers.each(leftBoxes, function(box) {
+ box.height = newMaxChartAreaHeight;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ box.height = newMaxChartAreaHeight;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ if (!box.options.fullWidth) {
+ box.width = newMaxChartAreaWidth;
+ }
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ if (!box.options.fullWidth) {
+ box.width = newMaxChartAreaWidth;
+ }
+ });
+
+ maxChartAreaHeight = newMaxChartAreaHeight;
+ maxChartAreaWidth = newMaxChartAreaWidth;
+ }
+
+ // Step 7 - Position the boxes
+ var left = xPadding;
+ var top = yPadding;
+
+ function placeBox(box) {
+ if (box.isHorizontal()) {
+ box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
+ box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
+ box.top = top;
+ box.bottom = top + box.height;
+
+ // Move to next point
+ top = box.bottom;
+
+ } else {
+
+ box.left = left;
+ box.right = left + box.width;
+ box.top = totalTopBoxesHeight;
+ box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
+
+ // Move to next point
+ left = box.right;
+ }
+ }
+
+ helpers.each(leftBoxes.concat(topBoxes), placeBox);
+
+ // Account for chart width and height
+ left += maxChartAreaWidth;
+ top += maxChartAreaHeight;
+
+ helpers.each(rightBoxes, placeBox);
+ helpers.each(bottomBoxes, placeBox);
+
+ // Step 8
+ chartInstance.chartArea = {
+ left: totalLeftBoxesWidth,
+ top: totalTopBoxesHeight,
+ right: totalLeftBoxesWidth + maxChartAreaWidth,
+ bottom: totalTopBoxesHeight + maxChartAreaHeight
+ };
+
+ // Step 9
+ helpers.each(chartAreaBoxes, function(box) {
+ box.left = chartInstance.chartArea.left;
+ box.top = chartInstance.chartArea.top;
+ box.right = chartInstance.chartArea.right;
+ box.bottom = chartInstance.chartArea.bottom;
+
+ box.update(maxChartAreaWidth, maxChartAreaHeight);
+ });
+ }
+ };
+};
+
+},{}],29:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ var noop = helpers.noop;
+
+ Chart.defaults.global.legend = {
+
+ display: true,
+ position: 'top',
+ fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
+ reverse: false,
+
+ // a callback that will handle
+ onClick: function(e, legendItem) {
+ var index = legendItem.datasetIndex;
+ var ci = this.chart;
+ var meta = ci.getDatasetMeta(index);
+
+ // See controller.isDatasetVisible comment
+ meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
+
+ // We hid a dataset ... rerender the chart
+ ci.update();
+ },
+
+ onHover: null,
+
+ labels: {
+ boxWidth: 40,
+ padding: 10,
+ // Generates labels shown in the legend
+ // Valid properties to return:
+ // text : text to display
+ // fillStyle : fill of coloured box
+ // strokeStyle: stroke of coloured box
+ // hidden : if this legend item refers to a hidden item
+ // lineCap : cap style for line
+ // lineDash
+ // lineDashOffset :
+ // lineJoin :
+ // lineWidth :
+ generateLabels: function(chart) {
+ var data = chart.data;
+ return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
+ return {
+ text: dataset.label,
+ fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
+ hidden: !chart.isDatasetVisible(i),
+ lineCap: dataset.borderCapStyle,
+ lineDash: dataset.borderDash,
+ lineDashOffset: dataset.borderDashOffset,
+ lineJoin: dataset.borderJoinStyle,
+ lineWidth: dataset.borderWidth,
+ strokeStyle: dataset.borderColor,
+ pointStyle: dataset.pointStyle,
+
+ // Below is extra data used for toggling the datasets
+ datasetIndex: i
+ };
+ }, this) : [];
+ }
+ }
+ };
+
+ Chart.Legend = Chart.Element.extend({
+
+ initialize: function(config) {
+ helpers.extend(this, config);
+
+ // Contains hit boxes for each dataset (in dataset order)
+ this.legendHitBoxes = [];
+
+ // Are we in doughnut mode which has a different data type
+ this.doughnutMode = false;
+ },
+
+ // These methods are ordered by lifecyle. Utilities then follow.
+ // Any function defined here is inherited by all legend types.
+ // Any function can be extended by the legend type
+
+ beforeUpdate: noop,
+ update: function(maxWidth, maxHeight, margins) {
+ var me = this;
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ me.beforeUpdate();
+
+ // Absorb the master measurements
+ me.maxWidth = maxWidth;
+ me.maxHeight = maxHeight;
+ me.margins = margins;
+
+ // Dimensions
+ me.beforeSetDimensions();
+ me.setDimensions();
+ me.afterSetDimensions();
+ // Labels
+ me.beforeBuildLabels();
+ me.buildLabels();
+ me.afterBuildLabels();
+
+ // Fit
+ me.beforeFit();
+ me.fit();
+ me.afterFit();
+ //
+ me.afterUpdate();
+
+ return me.minSize;
+ },
+ afterUpdate: noop,
+
+ //
+
+ beforeSetDimensions: noop,
+ setDimensions: function() {
+ var me = this;
+ // Set the unconstrained dimension before label rotation
+ if (me.isHorizontal()) {
+ // Reset position before calculating rotation
+ me.width = me.maxWidth;
+ me.left = 0;
+ me.right = me.width;
+ } else {
+ me.height = me.maxHeight;
+
+ // Reset position before calculating rotation
+ me.top = 0;
+ me.bottom = me.height;
+ }
+
+ // Reset padding
+ me.paddingLeft = 0;
+ me.paddingTop = 0;
+ me.paddingRight = 0;
+ me.paddingBottom = 0;
+
+ // Reset minSize
+ me.minSize = {
+ width: 0,
+ height: 0
+ };
+ },
+ afterSetDimensions: noop,
+
+ //
+
+ beforeBuildLabels: noop,
+ buildLabels: function() {
+ var me = this;
+ me.legendItems = me.options.labels.generateLabels.call(me, me.chart);
+ if (me.options.reverse) {
+ me.legendItems.reverse();
+ }
+ },
+ afterBuildLabels: noop,
+
+ //
+
+ beforeFit: noop,
+ fit: function() {
+ var me = this;
+ var opts = me.options;
+ var labelOpts = opts.labels;
+ var display = opts.display;
+
+ var ctx = me.ctx;
+
+ var globalDefault = Chart.defaults.global,
+ itemOrDefault = helpers.getValueOrDefault,
+ fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
+ fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
+ fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
+ labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+
+ // Reset hit boxes
+ var hitboxes = me.legendHitBoxes = [];
+
+ var minSize = me.minSize;
+ var isHorizontal = me.isHorizontal();
+
+ if (isHorizontal) {
+ minSize.width = me.maxWidth; // fill all the width
+ minSize.height = display ? 10 : 0;
+ } else {
+ minSize.width = display ? 10 : 0;
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ // Increase sizes here
+ if (display) {
+ ctx.font = labelFont;
+
+ if (isHorizontal) {
+ // Labels
+
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
+ var lineWidths = me.lineWidths = [0];
+ var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
+
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'top';
+
+ helpers.each(me.legendItems, function(legendItem, i) {
+ var boxWidth = labelOpts.usePointStyle ?
+ fontSize * Math.sqrt(2) :
+ labelOpts.boxWidth;
+
+ var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+ if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
+ totalHeight += fontSize + (labelOpts.padding);
+ lineWidths[lineWidths.length] = me.left;
+ }
+
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: width,
+ height: fontSize
+ };
+
+ lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
+ });
+
+ minSize.height += totalHeight;
+
+ } else {
+ var vPadding = labelOpts.padding;
+ var columnWidths = me.columnWidths = [];
+ var totalWidth = labelOpts.padding;
+ var currentColWidth = 0;
+ var currentColHeight = 0;
+ var itemHeight = fontSize + vPadding;
+
+ helpers.each(me.legendItems, function(legendItem, i) {
+ // If usePointStyle is set, multiple boxWidth by 2 since it represents
+ // the radius and not truly the width
+ var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth;
+
+ var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+
+ // If too tall, go to new column
+ if (currentColHeight + itemHeight > minSize.height) {
+ totalWidth += currentColWidth + labelOpts.padding;
+ columnWidths.push(currentColWidth); // previous column width
+
+ currentColWidth = 0;
+ currentColHeight = 0;
+ }
+
+ // Get max width
+ currentColWidth = Math.max(currentColWidth, itemWidth);
+ currentColHeight += itemHeight;
+
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: itemWidth,
+ height: fontSize
+ };
+ });
+
+ totalWidth += currentColWidth;
+ columnWidths.push(currentColWidth);
+ minSize.width += totalWidth;
+ }
+ }
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+ },
+ afterFit: noop,
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position === 'top' || this.options.position === 'bottom';
+ },
+
+ // Actualy draw the legend on the canvas
+ draw: function() {
+ var me = this;
+ var opts = me.options;
+ var labelOpts = opts.labels;
+ var globalDefault = Chart.defaults.global,
+ lineDefault = globalDefault.elements.line,
+ legendWidth = me.width,
+ lineWidths = me.lineWidths;
+
+ if (opts.display) {
+ var ctx = me.ctx,
+ cursor,
+ itemOrDefault = helpers.getValueOrDefault,
+ fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),
+ fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
+ fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
+ fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
+ labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+
+ // Canvas setup
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'top';
+ ctx.lineWidth = 0.5;
+ ctx.strokeStyle = fontColor; // for strikethrough effect
+ ctx.fillStyle = fontColor; // render in correct colour
+ ctx.font = labelFont;
+
+ var boxWidth = labelOpts.boxWidth,
+ hitboxes = me.legendHitBoxes;
+
+ // current position
+ var drawLegendBox = function(x, y, legendItem) {
+ if (isNaN(boxWidth) || boxWidth <= 0) {
+ return;
+ }
+
+ // Set the ctx for the box
+ ctx.save();
+
+ ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
+ ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
+ ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
+ ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
+ ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
+ ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
+ var isLineWidthZero = (itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
+
+ if (ctx.setLineDash) {
+ // IE 9 and 10 do not support line dash
+ ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
+ }
+
+ if (opts.labels && opts.labels.usePointStyle) {
+ // Recalulate x and y for drawPoint() because its expecting
+ // x and y to be center of figure (instead of top left)
+ var radius = fontSize * Math.SQRT2 / 2;
+ var offSet = radius / Math.SQRT2;
+ var centerX = x + offSet;
+ var centerY = y + offSet;
+
+ // Draw pointStyle as legend symbol
+ Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
+ } else {
+ // Draw box as legend symbol
+ if (!isLineWidthZero) {
+ ctx.strokeRect(x, y, boxWidth, fontSize);
+ }
+ ctx.fillRect(x, y, boxWidth, fontSize);
+ }
+
+ ctx.restore();
+ };
+ var fillText = function(x, y, legendItem, textWidth) {
+ ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);
+
+ if (legendItem.hidden) {
+ // Strikethrough the text if hidden
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));
+ ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));
+ ctx.stroke();
+ }
+ };
+
+ // Horizontal
+ var isHorizontal = me.isHorizontal();
+ if (isHorizontal) {
+ cursor = {
+ x: me.left + ((legendWidth - lineWidths[0]) / 2),
+ y: me.top + labelOpts.padding,
+ line: 0
+ };
+ } else {
+ cursor = {
+ x: me.left + labelOpts.padding,
+ y: me.top + labelOpts.padding,
+ line: 0
+ };
+ }
+
+ var itemHeight = fontSize + labelOpts.padding;
+ helpers.each(me.legendItems, function(legendItem, i) {
+ var textWidth = ctx.measureText(legendItem.text).width,
+ width = labelOpts.usePointStyle ?
+ fontSize + (fontSize / 2) + textWidth :
+ boxWidth + (fontSize / 2) + textWidth,
+ x = cursor.x,
+ y = cursor.y;
+
+ if (isHorizontal) {
+ if (x + width >= legendWidth) {
+ y = cursor.y += itemHeight;
+ cursor.line++;
+ x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
+ }
+ } else if (y + itemHeight > me.bottom) {
+ x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
+ y = cursor.y = me.top;
+ cursor.line++;
+ }
+
+ drawLegendBox(x, y, legendItem);
+
+ hitboxes[i].left = x;
+ hitboxes[i].top = y;
+
+ // Fill the actual label
+ fillText(x, y, legendItem, textWidth);
+
+ if (isHorizontal) {
+ cursor.x += width + (labelOpts.padding);
+ } else {
+ cursor.y += itemHeight;
+ }
+
+ });
+ }
+ },
+
+ // Handle an event
+ handleEvent: function(e) {
+ var me = this;
+ var opts = me.options;
+ var type = e.type === 'mouseup' ? 'click' : e.type;
+
+ if (type === 'mousemove') {
+ if (!opts.onHover) {
+ return;
+ }
+ } else if (type === 'click') {
+ if (!opts.onClick) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ var position = helpers.getRelativePosition(e, me.chart.chart),
+ x = position.x,
+ y = position.y;
+
+ if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
+ // See if we are touching one of the dataset boxes
+ var lh = me.legendHitBoxes;
+ for (var i = 0; i < lh.length; ++i) {
+ var hitBox = lh[i];
+
+ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
+ // Touching an element
+ if (type === 'click') {
+ opts.onClick.call(me, e, me.legendItems[i]);
+ break;
+ } else if (type === 'mousemove') {
+ opts.onHover.call(me, e, me.legendItems[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+ });
+
+ // Register the legend plugin
+ Chart.plugins.register({
+ beforeInit: function(chartInstance) {
+ var opts = chartInstance.options;
+ var legendOpts = opts.legend;
+
+ if (legendOpts) {
+ chartInstance.legend = new Chart.Legend({
+ ctx: chartInstance.chart.ctx,
+ options: legendOpts,
+ chart: chartInstance
+ });
+
+ Chart.layoutService.addBox(chartInstance, chartInstance.legend);
+ }
+ }
+ });
+};
+
+},{}],30:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var noop = Chart.helpers.noop;
+
+ /**
+ * The plugin service singleton
+ * @namespace Chart.plugins
+ * @since 2.1.0
+ */
+ Chart.plugins = {
+ _plugins: [],
+
+ /**
+ * Registers the given plugin(s) if not already registered.
+ * @param {Array|Object} plugins plugin instance(s).
+ */
+ register: function(plugins) {
+ var p = this._plugins;
+ ([]).concat(plugins).forEach(function(plugin) {
+ if (p.indexOf(plugin) === -1) {
+ p.push(plugin);
+ }
+ });
+ },
+
+ /**
+ * Unregisters the given plugin(s) only if registered.
+ * @param {Array|Object} plugins plugin instance(s).
+ */
+ unregister: function(plugins) {
+ var p = this._plugins;
+ ([]).concat(plugins).forEach(function(plugin) {
+ var idx = p.indexOf(plugin);
+ if (idx !== -1) {
+ p.splice(idx, 1);
+ }
+ });
+ },
+
+ /**
+ * Remove all registered p^lugins.
+ * @since 2.1.5
+ */
+ clear: function() {
+ this._plugins = [];
+ },
+
+ /**
+ * Returns the number of registered plugins?
+ * @returns {Number}
+ * @since 2.1.5
+ */
+ count: function() {
+ return this._plugins.length;
+ },
+
+ /**
+ * Returns all registered plugin intances.
+ * @returns {Array} array of plugin objects.
+ * @since 2.1.5
+ */
+ getAll: function() {
+ return this._plugins;
+ },
+
+ /**
+ * Calls registered plugins on the specified extension, with the given args. This
+ * method immediately returns as soon as a plugin explicitly returns false. The
+ * returned value can be used, for instance, to interrupt the current action.
+ * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate').
+ * @param {Array} [args] extra arguments to apply to the extension call.
+ * @returns {Boolean} false if any of the plugins return false, else returns true.
+ */
+ notify: function(extension, args) {
+ var plugins = this._plugins;
+ var ilen = plugins.length;
+ var i, plugin;
+
+ for (i=0; i tickWidth && me.labelRotation < optionTicks.maxRotation) {
+ cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
+ sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
+
+ firstRotated = cosRotation * firstWidth;
+
+ // We're right aligning the text now.
+ if (firstRotated + tickFontSize / 2 > me.yLabelWidth) {
+ me.paddingLeft = firstRotated + tickFontSize / 2;
+ }
+
+ me.paddingRight = tickFontSize / 2;
+
+ if (sinRotation * originalLabelWidth > me.maxHeight) {
+ // go back one step
+ me.labelRotation--;
+ break;
+ }
+
+ me.labelRotation++;
+ labelWidth = cosRotation * originalLabelWidth;
+ }
+ }
+ }
+
+ if (me.margins) {
+ me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
+ me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
+ }
+ },
+ afterCalculateTickRotation: function() {
+ helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
+ },
+
+ //
+
+ beforeFit: function() {
+ helpers.callCallback(this.options.beforeFit, [this]);
+ },
+ fit: function() {
+ var me = this;
+ // Reset
+ var minSize = me.minSize = {
+ width: 0,
+ height: 0
+ };
+
+ var opts = me.options;
+ var globalDefaults = Chart.defaults.global;
+ var tickOpts = opts.ticks;
+ var scaleLabelOpts = opts.scaleLabel;
+ var gridLineOpts = opts.gridLines;
+ var display = opts.display;
+ var isHorizontal = me.isHorizontal();
+
+ var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
+ var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
+
+ var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize);
+
+ var tickMarkLength = opts.gridLines.tickMarkLength;
+
+ // Width
+ if (isHorizontal) {
+ // subtract the margins to line up with the chartArea if we are a full width scale
+ minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
+ } else {
+ minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
+ }
+
+ // height
+ if (isHorizontal) {
+ minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
+ } else {
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ // Are we showing a title for the scale?
+ if (scaleLabelOpts.display && display) {
+ if (isHorizontal) {
+ minSize.height += (scaleLabelFontSize * 1.5);
+ } else {
+ minSize.width += (scaleLabelFontSize * 1.5);
+ }
+ }
+
+ if (tickOpts.display && display) {
+ // Don't bother fitting the ticks if we are not showing them
+ if (!me.longestTextCache) {
+ me.longestTextCache = {};
+ }
+
+ var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache);
+ var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks);
+ var lineSpace = tickFontSize * 0.5;
+
+ if (isHorizontal) {
+ // A horizontal axis is more constrained by the height.
+ me.longestLabelWidth = largestTextWidth;
+
+ // TODO - improve this calculation
+ var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines);
+
+ minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight);
+ me.ctx.font = tickLabelFont;
+
+ var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width;
+ var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width;
+
+ // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
+ // by the font height
+ var cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
+ var sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
+ me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
+ me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
+ } else {
+ // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
+ var maxLabelWidth = me.maxWidth - minSize.width;
+
+ // Account for padding
+ var mirror = tickOpts.mirror;
+ if (!mirror) {
+ largestTextWidth += me.options.ticks.padding;
+ } else {
+ // If mirrored text is on the inside so don't expand
+ largestTextWidth = 0;
+ }
+
+ if (largestTextWidth < maxLabelWidth) {
+ // We don't need all the room
+ minSize.width += largestTextWidth;
+ } else {
+ // Expand to max size
+ minSize.width = me.maxWidth;
+ }
+
+ me.paddingTop = tickFontSize / 2;
+ me.paddingBottom = tickFontSize / 2;
+ }
+ }
+
+ if (me.margins) {
+ me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
+ me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
+ me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
+ me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
+ }
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+
+ },
+ afterFit: function() {
+ helpers.callCallback(this.options.afterFit, [this]);
+ },
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position === 'top' || this.options.position === 'bottom';
+ },
+ isFullWidth: function() {
+ return (this.options.fullWidth);
+ },
+
+ // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
+ getRightValue: function(rawValue) {
+ // Null and undefined values first
+ if (rawValue === null || typeof(rawValue) === 'undefined') {
+ return NaN;
+ }
+ // isNaN(object) returns true, so make sure NaN is checking for a number
+ if (typeof(rawValue) === 'number' && isNaN(rawValue)) {
+ return NaN;
+ }
+ // If it is in fact an object, dive in one more level
+ if (typeof(rawValue) === 'object') {
+ if ((rawValue instanceof Date) || (rawValue.isValid)) {
+ return rawValue;
+ }
+ return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
+ }
+
+ // Value is good, return it
+ return rawValue;
+ },
+
+ // Used to get the value to display in the tooltip for the data at the given index
+ // function getLabelForIndex(index, datasetIndex)
+ getLabelForIndex: helpers.noop,
+
+ // Used to get data value locations. Value can either be an index or a numerical value
+ getPixelForValue: helpers.noop,
+
+ // Used to get the data value from a given pixel. This is the inverse of getPixelForValue
+ getValueForPixel: helpers.noop,
+
+ // Used for tick location, should
+ getPixelForTick: function(index, includeOffset) {
+ var me = this;
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+ var pixel = (tickWidth * index) + me.paddingLeft;
+
+ if (includeOffset) {
+ pixel += tickWidth / 2;
+ }
+
+ var finalVal = me.left + Math.round(pixel);
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
+ return finalVal;
+ }
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
+ return me.top + (index * (innerHeight / (me.ticks.length - 1)));
+ },
+
+ // Utility for getting the pixel location of a percentage of scale
+ getPixelForDecimal: function(decimal /* , includeOffset*/) {
+ var me = this;
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var valueOffset = (innerWidth * decimal) + me.paddingLeft;
+
+ var finalVal = me.left + Math.round(valueOffset);
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
+ return finalVal;
+ }
+ return me.top + (decimal * me.height);
+ },
+
+ getBasePixel: function() {
+ var me = this;
+ var min = me.min;
+ var max = me.max;
+
+ return me.getPixelForValue(
+ me.beginAtZero? 0:
+ min < 0 && max < 0? max :
+ min > 0 && max > 0? min :
+ 0);
+ },
+
+ // Actualy draw the scale on the canvas
+ // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
+ draw: function(chartArea) {
+ var me = this;
+ var options = me.options;
+ if (!options.display) {
+ return;
+ }
+
+ var context = me.ctx;
+ var globalDefaults = Chart.defaults.global;
+ var optionTicks = options.ticks;
+ var gridLines = options.gridLines;
+ var scaleLabel = options.scaleLabel;
+
+ var isRotated = me.labelRotation !== 0;
+ var skipRatio;
+ var useAutoskipper = optionTicks.autoSkip;
+ var isHorizontal = me.isHorizontal();
+
+ // figure out the maximum number of gridlines to show
+ var maxTicks;
+ if (optionTicks.maxTicksLimit) {
+ maxTicks = optionTicks.maxTicksLimit;
+ }
+
+ var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
+ var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
+ var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
+ var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
+ var tl = gridLines.tickMarkLength;
+ var borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
+ var borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
+
+ var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
+ var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize);
+ var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle);
+ var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily);
+ var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
+
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
+ var cosRotation = Math.cos(labelRotationRadians);
+ var longestRotatedLabel = me.longestLabelWidth * cosRotation;
+
+ // Make sure we draw text in the correct color and font
+ context.fillStyle = tickFontColor;
+
+ var itemsToDraw = [];
+
+ if (isHorizontal) {
+ skipRatio = false;
+
+ // Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation
+ // See #2584
+ if (isRotated) {
+ longestRotatedLabel /= 2;
+ }
+
+ if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) {
+ skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight)));
+ }
+
+ // if they defined a max number of optionTicks,
+ // increase skipRatio until that number is met
+ if (maxTicks && me.ticks.length > maxTicks) {
+ while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) {
+ if (!skipRatio) {
+ skipRatio = 1;
+ }
+ skipRatio += 1;
+ }
+ }
+
+ if (!useAutoskipper) {
+ skipRatio = false;
+ }
+ }
+
+
+ var xTickStart = options.position === 'right' ? me.left : me.right - tl;
+ var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
+ var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
+ var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
+
+ helpers.each(me.ticks, function(label, index) {
+ // If the callback returned a null or undefined value, do not draw this line
+ if (label === undefined || label === null) {
+ return;
+ }
+
+ var isLastTick = me.ticks.length === index + 1;
+
+ // Since we always show the last tick,we need may need to hide the last shown one before
+ var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length);
+ if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
+ return;
+ }
+
+ var lineWidth, lineColor;
+ if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) {
+ // Draw the first index specially
+ lineWidth = gridLines.zeroLineWidth;
+ lineColor = gridLines.zeroLineColor;
+ } else {
+ lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index);
+ lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index);
+ }
+
+ // Common properties
+ var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
+ var textAlign = 'middle';
+ var textBaseline = 'middle';
+
+ if (isHorizontal) {
+ if (!isRotated) {
+ textBaseline = options.position === 'top' ? 'bottom' : 'top';
+ }
+
+ textAlign = isRotated ? 'right' : 'center';
+
+ var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines
+ labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
+ labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl;
+
+ tx1 = tx2 = x1 = x2 = xLineValue;
+ ty1 = yTickStart;
+ ty2 = yTickEnd;
+ y1 = chartArea.top;
+ y2 = chartArea.bottom;
+ } else {
+ if (options.position === 'left') {
+ if (optionTicks.mirror) {
+ labelX = me.right + optionTicks.padding;
+ textAlign = 'left';
+ } else {
+ labelX = me.right - optionTicks.padding;
+ textAlign = 'right';
+ }
+ // right side
+ } else if (optionTicks.mirror) {
+ labelX = me.left - optionTicks.padding;
+ textAlign = 'right';
+ } else {
+ labelX = me.left + optionTicks.padding;
+ textAlign = 'left';
+ }
+
+ var yLineValue = me.getPixelForTick(index); // xvalues for grid lines
+ yLineValue += helpers.aliasPixel(lineWidth);
+ labelY = me.getPixelForTick(index, gridLines.offsetGridLines);
+
+ tx1 = xTickStart;
+ tx2 = xTickEnd;
+ x1 = chartArea.left;
+ x2 = chartArea.right;
+ ty1 = ty2 = y1 = y2 = yLineValue;
+ }
+
+ itemsToDraw.push({
+ tx1: tx1,
+ ty1: ty1,
+ tx2: tx2,
+ ty2: ty2,
+ x1: x1,
+ y1: y1,
+ x2: x2,
+ y2: y2,
+ labelX: labelX,
+ labelY: labelY,
+ glWidth: lineWidth,
+ glColor: lineColor,
+ glBorderDash: borderDash,
+ glBorderDashOffset: borderDashOffset,
+ rotation: -1 * labelRotationRadians,
+ label: label,
+ textBaseline: textBaseline,
+ textAlign: textAlign
+ });
+ });
+
+ // Draw all of the tick labels, tick marks, and grid lines at the correct places
+ helpers.each(itemsToDraw, function(itemToDraw) {
+ if (gridLines.display) {
+ context.save();
+ context.lineWidth = itemToDraw.glWidth;
+ context.strokeStyle = itemToDraw.glColor;
+ if (context.setLineDash) {
+ context.setLineDash(itemToDraw.glBorderDash);
+ context.lineDashOffset = itemToDraw.glBorderDashOffset;
+ }
+
+ context.beginPath();
+
+ if (gridLines.drawTicks) {
+ context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
+ context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
+ }
+
+ if (gridLines.drawOnChartArea) {
+ context.moveTo(itemToDraw.x1, itemToDraw.y1);
+ context.lineTo(itemToDraw.x2, itemToDraw.y2);
+ }
+
+ context.stroke();
+ context.restore();
+ }
+
+ if (optionTicks.display) {
+ context.save();
+ context.translate(itemToDraw.labelX, itemToDraw.labelY);
+ context.rotate(itemToDraw.rotation);
+ context.font = tickLabelFont;
+ context.textBaseline = itemToDraw.textBaseline;
+ context.textAlign = itemToDraw.textAlign;
+
+ var label = itemToDraw.label;
+ if (helpers.isArray(label)) {
+ for (var i = 0, y = -(label.length - 1)*tickFontSize*0.75; i < label.length; ++i) {
+ // We just make sure the multiline element is a string here..
+ context.fillText('' + label[i], 0, y);
+ // apply same lineSpacing as calculated @ L#320
+ y += (tickFontSize * 1.5);
+ }
+ } else {
+ context.fillText(label, 0, 0);
+ }
+ context.restore();
+ }
+ });
+
+ if (scaleLabel.display) {
+ // Draw the scale label
+ var scaleLabelX;
+ var scaleLabelY;
+ var rotation = 0;
+
+ if (isHorizontal) {
+ scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
+ scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2);
+ } else {
+ var isLeft = options.position === 'left';
+ scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2);
+ scaleLabelY = me.top + ((me.bottom - me.top) / 2);
+ rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
+ }
+
+ context.save();
+ context.translate(scaleLabelX, scaleLabelY);
+ context.rotate(rotation);
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+ context.fillStyle = scaleLabelFontColor; // render in correct colour
+ context.font = scaleLabelFont;
+ context.fillText(scaleLabel.labelString, 0, 0);
+ context.restore();
+ }
+
+ if (gridLines.drawBorder) {
+ // Draw the line at the edge of the axis
+ context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0);
+ context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0);
+ var x1 = me.left,
+ x2 = me.right,
+ y1 = me.top,
+ y2 = me.bottom;
+
+ var aliasPixel = helpers.aliasPixel(context.lineWidth);
+ if (isHorizontal) {
+ y1 = y2 = options.position === 'top' ? me.bottom : me.top;
+ y1 += aliasPixel;
+ y2 += aliasPixel;
+ } else {
+ x1 = x2 = options.position === 'left' ? me.right : me.left;
+ x1 += aliasPixel;
+ x2 += aliasPixel;
+ }
+
+ context.beginPath();
+ context.moveTo(x1, y1);
+ context.lineTo(x2, y2);
+ context.stroke();
+ }
+ }
+ });
+};
+
+},{}],32:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.scaleService = {
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
+ // use the new chart options to grab the correct scale
+ constructors: {},
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
+ // old browsers
+
+ // Scale config defaults
+ defaults: {},
+ registerScaleType: function(type, scaleConstructor, defaults) {
+ this.constructors[type] = scaleConstructor;
+ this.defaults[type] = helpers.clone(defaults);
+ },
+ getScaleConstructor: function(type) {
+ return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
+ },
+ getScaleDefaults: function(type) {
+ // Return the scale defaults merged with the global settings so that we always use the latest ones
+ return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
+ },
+ updateScaleDefaults: function(type, additions) {
+ var defaults = this.defaults;
+ if (defaults.hasOwnProperty(type)) {
+ defaults[type] = helpers.extend(defaults[type], additions);
+ }
+ },
+ addScalesToLayout: function(chartInstance) {
+ // Adds each scale to the chart.boxes array to be sized accordingly
+ helpers.each(chartInstance.scales, function(scale) {
+ Chart.layoutService.addBox(chartInstance, scale);
+ });
+ }
+ };
+};
+
+},{}],33:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.global.title = {
+ display: false,
+ position: 'top',
+ fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
+
+ fontStyle: 'bold',
+ padding: 10,
+
+ // actual title
+ text: ''
+ };
+
+ var noop = helpers.noop;
+ Chart.Title = Chart.Element.extend({
+
+ initialize: function(config) {
+ var me = this;
+ helpers.extend(me, config);
+ me.options = helpers.configMerge(Chart.defaults.global.title, config.options);
+
+ // Contains hit boxes for each dataset (in dataset order)
+ me.legendHitBoxes = [];
+ },
+
+ // These methods are ordered by lifecyle. Utilities then follow.
+
+ beforeUpdate: function() {
+ var chartOpts = this.chart.options;
+ if (chartOpts && chartOpts.title) {
+ this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title);
+ }
+ },
+ update: function(maxWidth, maxHeight, margins) {
+ var me = this;
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ me.beforeUpdate();
+
+ // Absorb the master measurements
+ me.maxWidth = maxWidth;
+ me.maxHeight = maxHeight;
+ me.margins = margins;
+
+ // Dimensions
+ me.beforeSetDimensions();
+ me.setDimensions();
+ me.afterSetDimensions();
+ // Labels
+ me.beforeBuildLabels();
+ me.buildLabels();
+ me.afterBuildLabels();
+
+ // Fit
+ me.beforeFit();
+ me.fit();
+ me.afterFit();
+ //
+ me.afterUpdate();
+
+ return me.minSize;
+
+ },
+ afterUpdate: noop,
+
+ //
+
+ beforeSetDimensions: noop,
+ setDimensions: function() {
+ var me = this;
+ // Set the unconstrained dimension before label rotation
+ if (me.isHorizontal()) {
+ // Reset position before calculating rotation
+ me.width = me.maxWidth;
+ me.left = 0;
+ me.right = me.width;
+ } else {
+ me.height = me.maxHeight;
+
+ // Reset position before calculating rotation
+ me.top = 0;
+ me.bottom = me.height;
+ }
+
+ // Reset padding
+ me.paddingLeft = 0;
+ me.paddingTop = 0;
+ me.paddingRight = 0;
+ me.paddingBottom = 0;
+
+ // Reset minSize
+ me.minSize = {
+ width: 0,
+ height: 0
+ };
+ },
+ afterSetDimensions: noop,
+
+ //
+
+ beforeBuildLabels: noop,
+ buildLabels: noop,
+ afterBuildLabels: noop,
+
+ //
+
+ beforeFit: noop,
+ fit: function() {
+ var me = this,
+ valueOrDefault = helpers.getValueOrDefault,
+ opts = me.options,
+ globalDefaults = Chart.defaults.global,
+ display = opts.display,
+ fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
+ minSize = me.minSize;
+
+ if (me.isHorizontal()) {
+ minSize.width = me.maxWidth; // fill all the width
+ minSize.height = display ? fontSize + (opts.padding * 2) : 0;
+ } else {
+ minSize.width = display ? fontSize + (opts.padding * 2) : 0;
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+
+ },
+ afterFit: noop,
+
+ // Shared Methods
+ isHorizontal: function() {
+ var pos = this.options.position;
+ return pos === 'top' || pos === 'bottom';
+ },
+
+ // Actualy draw the title block on the canvas
+ draw: function() {
+ var me = this,
+ ctx = me.ctx,
+ valueOrDefault = helpers.getValueOrDefault,
+ opts = me.options,
+ globalDefaults = Chart.defaults.global;
+
+ if (opts.display) {
+ var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
+ fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle),
+ fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),
+ titleFont = helpers.fontString(fontSize, fontStyle, fontFamily),
+ rotation = 0,
+ titleX,
+ titleY,
+ top = me.top,
+ left = me.left,
+ bottom = me.bottom,
+ right = me.right;
+
+ ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
+ ctx.font = titleFont;
+
+ // Horizontal
+ if (me.isHorizontal()) {
+ titleX = left + ((right - left) / 2); // midpoint of the width
+ titleY = top + ((bottom - top) / 2); // midpoint of the height
+ } else {
+ titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2);
+ titleY = top + ((bottom - top) / 2);
+ rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
+ }
+
+ ctx.save();
+ ctx.translate(titleX, titleY);
+ ctx.rotate(rotation);
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(opts.text, 0, 0);
+ ctx.restore();
+ }
+ }
+ });
+
+ // Register the title plugin
+ Chart.plugins.register({
+ beforeInit: function(chartInstance) {
+ var opts = chartInstance.options;
+ var titleOpts = opts.title;
+
+ if (titleOpts) {
+ chartInstance.titleBlock = new Chart.Title({
+ ctx: chartInstance.chart.ctx,
+ options: titleOpts,
+ chart: chartInstance
+ });
+
+ Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock);
+ }
+ }
+ });
+};
+
+},{}],34:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ Chart.defaults.global.tooltips = {
+ enabled: true,
+ custom: null,
+ mode: 'single',
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ titleFontStyle: 'bold',
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+ titleFontColor: '#fff',
+ titleAlign: 'left',
+ bodySpacing: 2,
+ bodyFontColor: '#fff',
+ bodyAlign: 'left',
+ footerFontStyle: 'bold',
+ footerSpacing: 2,
+ footerMarginTop: 6,
+ footerFontColor: '#fff',
+ footerAlign: 'left',
+ yPadding: 6,
+ xPadding: 6,
+ yAlign: 'center',
+ xAlign: 'center',
+ caretSize: 5,
+ cornerRadius: 6,
+ multiKeyBackground: '#fff',
+ callbacks: {
+ // Args are: (tooltipItems, data)
+ beforeTitle: helpers.noop,
+ title: function(tooltipItems, data) {
+ // Pick first xLabel for now
+ var title = '';
+ var labels = data.labels;
+ var labelCount = labels ? labels.length : 0;
+
+ if (tooltipItems.length > 0) {
+ var item = tooltipItems[0];
+
+ if (item.xLabel) {
+ title = item.xLabel;
+ } else if (labelCount > 0 && item.index < labelCount) {
+ title = labels[item.index];
+ }
+ }
+
+ return title;
+ },
+ afterTitle: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ beforeBody: helpers.noop,
+
+ // Args are: (tooltipItem, data)
+ beforeLabel: helpers.noop,
+ label: function(tooltipItem, data) {
+ var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
+ return datasetLabel + ': ' + tooltipItem.yLabel;
+ },
+ labelColor: function(tooltipItem, chartInstance) {
+ var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex);
+ var activeElement = meta.data[tooltipItem.index];
+ var view = activeElement._view;
+ return {
+ borderColor: view.borderColor,
+ backgroundColor: view.backgroundColor
+ };
+ },
+ afterLabel: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ afterBody: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ beforeFooter: helpers.noop,
+ footer: helpers.noop,
+ afterFooter: helpers.noop
+ }
+ };
+
+ // Helper to push or concat based on if the 2nd parameter is an array or not
+ function pushOrConcat(base, toPush) {
+ if (toPush) {
+ if (helpers.isArray(toPush)) {
+ // base = base.concat(toPush);
+ Array.prototype.push.apply(base, toPush);
+ } else {
+ base.push(toPush);
+ }
+ }
+
+ return base;
+ }
+
+ function getAveragePosition(elements) {
+ if (!elements.length) {
+ return false;
+ }
+
+ var i, len;
+ var xPositions = [];
+ var yPositions = [];
+
+ for (i = 0, len = elements.length; i < len; ++i) {
+ var el = elements[i];
+ if (el && el.hasValue()) {
+ var pos = el.tooltipPosition();
+ xPositions.push(pos.x);
+ yPositions.push(pos.y);
+ }
+ }
+
+ var x = 0,
+ y = 0;
+ for (i = 0; i < xPositions.length; ++i) {
+ if (xPositions[i]) {
+ x += xPositions[i];
+ y += yPositions[i];
+ }
+ }
+
+ return {
+ x: Math.round(x / xPositions.length),
+ y: Math.round(y / xPositions.length)
+ };
+ }
+
+ // Private helper to create a tooltip iteam model
+ // @param element : the chart element (point, arc, bar) to create the tooltip item for
+ // @return : new tooltip item
+ function createTooltipItem(element) {
+ var xScale = element._xScale;
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
+ var index = element._index,
+ datasetIndex = element._datasetIndex;
+
+ return {
+ xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
+ yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
+ index: index,
+ datasetIndex: datasetIndex
+ };
+ }
+
+ Chart.Tooltip = Chart.Element.extend({
+ initialize: function() {
+ var me = this;
+ var globalDefaults = Chart.defaults.global;
+ var tooltipOpts = me._options;
+ var getValueOrDefault = helpers.getValueOrDefault;
+
+ helpers.extend(me, {
+ _model: {
+ // Positioning
+ xPadding: tooltipOpts.xPadding,
+ yPadding: tooltipOpts.yPadding,
+ xAlign: tooltipOpts.xAlign,
+ yAlign: tooltipOpts.yAlign,
+
+ // Body
+ bodyFontColor: tooltipOpts.bodyFontColor,
+ _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
+ _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
+ _bodyAlign: tooltipOpts.bodyAlign,
+ bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
+ bodySpacing: tooltipOpts.bodySpacing,
+
+ // Title
+ titleFontColor: tooltipOpts.titleFontColor,
+ _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
+ _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
+ titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
+ _titleAlign: tooltipOpts.titleAlign,
+ titleSpacing: tooltipOpts.titleSpacing,
+ titleMarginBottom: tooltipOpts.titleMarginBottom,
+
+ // Footer
+ footerFontColor: tooltipOpts.footerFontColor,
+ _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
+ _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
+ footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
+ _footerAlign: tooltipOpts.footerAlign,
+ footerSpacing: tooltipOpts.footerSpacing,
+ footerMarginTop: tooltipOpts.footerMarginTop,
+
+ // Appearance
+ caretSize: tooltipOpts.caretSize,
+ cornerRadius: tooltipOpts.cornerRadius,
+ backgroundColor: tooltipOpts.backgroundColor,
+ opacity: 0,
+ legendColorBackground: tooltipOpts.multiKeyBackground
+ }
+ });
+ },
+
+ // Get the title
+ // Args are: (tooltipItem, data)
+ getTitle: function() {
+ var me = this;
+ var opts = me._options;
+ var callbacks = opts.callbacks;
+
+ var beforeTitle = callbacks.beforeTitle.apply(me, arguments),
+ title = callbacks.title.apply(me, arguments),
+ afterTitle = callbacks.afterTitle.apply(me, arguments);
+
+ var lines = [];
+ lines = pushOrConcat(lines, beforeTitle);
+ lines = pushOrConcat(lines, title);
+ lines = pushOrConcat(lines, afterTitle);
+
+ return lines;
+ },
+
+ // Args are: (tooltipItem, data)
+ getBeforeBody: function() {
+ var lines = this._options.callbacks.beforeBody.apply(this, arguments);
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+ },
+
+ // Args are: (tooltipItem, data)
+ getBody: function(tooltipItems, data) {
+ var me = this;
+ var callbacks = me._options.callbacks;
+ var bodyItems = [];
+
+ helpers.each(tooltipItems, function(tooltipItem) {
+ var bodyItem = {
+ before: [],
+ lines: [],
+ after: []
+ };
+ pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
+ pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
+ pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
+
+ bodyItems.push(bodyItem);
+ });
+
+ return bodyItems;
+ },
+
+ // Args are: (tooltipItem, data)
+ getAfterBody: function() {
+ var lines = this._options.callbacks.afterBody.apply(this, arguments);
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+ },
+
+ // Get the footer and beforeFooter and afterFooter lines
+ // Args are: (tooltipItem, data)
+ getFooter: function() {
+ var me = this;
+ var callbacks = me._options.callbacks;
+
+ var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
+ var footer = callbacks.footer.apply(me, arguments);
+ var afterFooter = callbacks.afterFooter.apply(me, arguments);
+
+ var lines = [];
+ lines = pushOrConcat(lines, beforeFooter);
+ lines = pushOrConcat(lines, footer);
+ lines = pushOrConcat(lines, afterFooter);
+
+ return lines;
+ },
+
+ update: function(changed) {
+ var me = this;
+ var opts = me._options;
+ var model = me._model;
+ var active = me._active;
+
+ var data = me._data;
+ var chartInstance = me._chartInstance;
+
+ var i, len;
+
+ if (active.length) {
+ model.opacity = 1;
+
+ var labelColors = [],
+ tooltipPosition = getAveragePosition(active);
+
+ var tooltipItems = [];
+ for (i = 0, len = active.length; i < len; ++i) {
+ tooltipItems.push(createTooltipItem(active[i]));
+ }
+
+ // If the user provided a sorting function, use it to modify the tooltip items
+ if (opts.itemSort) {
+ tooltipItems = tooltipItems.sort(function(a, b) {
+ return opts.itemSort(a, b, data);
+ });
+ }
+
+ // If there is more than one item, show color items
+ if (active.length > 1) {
+ helpers.each(tooltipItems, function(tooltipItem) {
+ labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance));
+ });
+ }
+
+ // Build the Text Lines
+ helpers.extend(model, {
+ title: me.getTitle(tooltipItems, data),
+ beforeBody: me.getBeforeBody(tooltipItems, data),
+ body: me.getBody(tooltipItems, data),
+ afterBody: me.getAfterBody(tooltipItems, data),
+ footer: me.getFooter(tooltipItems, data),
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
+ labelColors: labelColors
+ });
+
+ // We need to determine alignment of
+ var tooltipSize = me.getTooltipSize(model);
+ me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
+
+ helpers.extend(model, me.getBackgroundPoint(model, tooltipSize));
+ } else {
+ me._model.opacity = 0;
+ }
+
+ if (changed && opts.custom) {
+ opts.custom.call(me, model);
+ }
+
+ return me;
+ },
+ getTooltipSize: function(vm) {
+ var ctx = this._chart.ctx;
+
+ var size = {
+ height: vm.yPadding * 2, // Tooltip Padding
+ width: 0
+ };
+
+ // Count of all lines in the body
+ var body = vm.body;
+ var combinedBodyLength = body.reduce(function(count, bodyItem) {
+ return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
+ }, 0);
+ combinedBodyLength += vm.beforeBody.length + vm.afterBody.length;
+
+ var titleLineCount = vm.title.length;
+ var footerLineCount = vm.footer.length;
+ var titleFontSize = vm.titleFontSize,
+ bodyFontSize = vm.bodyFontSize,
+ footerFontSize = vm.footerFontSize;
+
+ size.height += titleLineCount * titleFontSize; // Title Lines
+ size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing
+ size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin
+ size.height += combinedBodyLength * bodyFontSize; // Body Lines
+ size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
+ size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin
+ size.height += footerLineCount * (footerFontSize); // Footer Lines
+ size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing
+
+ // Title width
+ var widthPadding = 0;
+ var maxLineWidth = function(line) {
+ size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding);
+ };
+
+ ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+ helpers.each(vm.title, maxLineWidth);
+
+ // Body width
+ ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+ helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth);
+
+ // Body lines may include some extra width due to the color box
+ widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0;
+ helpers.each(body, function(bodyItem) {
+ helpers.each(bodyItem.before, maxLineWidth);
+ helpers.each(bodyItem.lines, maxLineWidth);
+ helpers.each(bodyItem.after, maxLineWidth);
+ });
+
+ // Reset back to 0
+ widthPadding = 0;
+
+ // Footer width
+ ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+ helpers.each(vm.footer, maxLineWidth);
+
+ // Add padding
+ size.width += 2 * vm.xPadding;
+
+ return size;
+ },
+ determineAlignment: function(size) {
+ var me = this;
+ var model = me._model;
+ var chart = me._chart;
+ var chartArea = me._chartInstance.chartArea;
+
+ if (model.y < size.height) {
+ model.yAlign = 'top';
+ } else if (model.y > (chart.height - size.height)) {
+ model.yAlign = 'bottom';
+ }
+
+ var lf, rf; // functions to determine left, right alignment
+ var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
+ var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
+ var midX = (chartArea.left + chartArea.right) / 2;
+ var midY = (chartArea.top + chartArea.bottom) / 2;
+
+ if (model.yAlign === 'center') {
+ lf = function(x) {
+ return x <= midX;
+ };
+ rf = function(x) {
+ return x > midX;
+ };
+ } else {
+ lf = function(x) {
+ return x <= (size.width / 2);
+ };
+ rf = function(x) {
+ return x >= (chart.width - (size.width / 2));
+ };
+ }
+
+ olf = function(x) {
+ return x + size.width > chart.width;
+ };
+ orf = function(x) {
+ return x - size.width < 0;
+ };
+ yf = function(y) {
+ return y <= midY ? 'top' : 'bottom';
+ };
+
+ if (lf(model.x)) {
+ model.xAlign = 'left';
+
+ // Is tooltip too wide and goes over the right side of the chart.?
+ if (olf(model.x)) {
+ model.xAlign = 'center';
+ model.yAlign = yf(model.y);
+ }
+ } else if (rf(model.x)) {
+ model.xAlign = 'right';
+
+ // Is tooltip too wide and goes outside left edge of canvas?
+ if (orf(model.x)) {
+ model.xAlign = 'center';
+ model.yAlign = yf(model.y);
+ }
+ }
+ },
+ getBackgroundPoint: function(vm, size) {
+ // Background Position
+ var pt = {
+ x: vm.x,
+ y: vm.y
+ };
+
+ var caretSize = vm.caretSize,
+ caretPadding = vm.caretPadding,
+ cornerRadius = vm.cornerRadius,
+ xAlign = vm.xAlign,
+ yAlign = vm.yAlign,
+ paddingAndSize = caretSize + caretPadding,
+ radiusAndPadding = cornerRadius + caretPadding;
+
+ if (xAlign === 'right') {
+ pt.x -= size.width;
+ } else if (xAlign === 'center') {
+ pt.x -= (size.width / 2);
+ }
+
+ if (yAlign === 'top') {
+ pt.y += paddingAndSize;
+ } else if (yAlign === 'bottom') {
+ pt.y -= size.height + paddingAndSize;
+ } else {
+ pt.y -= (size.height / 2);
+ }
+
+ if (yAlign === 'center') {
+ if (xAlign === 'left') {
+ pt.x += paddingAndSize;
+ } else if (xAlign === 'right') {
+ pt.x -= paddingAndSize;
+ }
+ } else if (xAlign === 'left') {
+ pt.x -= radiusAndPadding;
+ } else if (xAlign === 'right') {
+ pt.x += radiusAndPadding;
+ }
+
+ return pt;
+ },
+ drawCaret: function(tooltipPoint, size, opacity) {
+ var vm = this._view;
+ var ctx = this._chart.ctx;
+ var x1, x2, x3;
+ var y1, y2, y3;
+ var caretSize = vm.caretSize;
+ var cornerRadius = vm.cornerRadius;
+ var xAlign = vm.xAlign,
+ yAlign = vm.yAlign;
+ var ptX = tooltipPoint.x,
+ ptY = tooltipPoint.y;
+ var width = size.width,
+ height = size.height;
+
+ if (yAlign === 'center') {
+ // Left or right side
+ if (xAlign === 'left') {
+ x1 = ptX;
+ x2 = x1 - caretSize;
+ x3 = x1;
+ } else {
+ x1 = ptX + width;
+ x2 = x1 + caretSize;
+ x3 = x1;
+ }
+
+ y2 = ptY + (height / 2);
+ y1 = y2 - caretSize;
+ y3 = y2 + caretSize;
+ } else {
+ if (xAlign === 'left') {
+ x1 = ptX + cornerRadius;
+ x2 = x1 + caretSize;
+ x3 = x2 + caretSize;
+ } else if (xAlign === 'right') {
+ x1 = ptX + width - cornerRadius;
+ x2 = x1 - caretSize;
+ x3 = x2 - caretSize;
+ } else {
+ x2 = ptX + (width / 2);
+ x1 = x2 - caretSize;
+ x3 = x2 + caretSize;
+ }
+
+ if (yAlign === 'top') {
+ y1 = ptY;
+ y2 = y1 - caretSize;
+ y3 = y1;
+ } else {
+ y1 = ptY + height;
+ y2 = y1 + caretSize;
+ y3 = y1;
+ }
+ }
+
+ var bgColor = helpers.color(vm.backgroundColor);
+ ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.lineTo(x3, y3);
+ ctx.closePath();
+ ctx.fill();
+ },
+ drawTitle: function(pt, vm, ctx, opacity) {
+ var title = vm.title;
+
+ if (title.length) {
+ ctx.textAlign = vm._titleAlign;
+ ctx.textBaseline = 'top';
+
+ var titleFontSize = vm.titleFontSize,
+ titleSpacing = vm.titleSpacing;
+
+ var titleFontColor = helpers.color(vm.titleFontColor);
+ ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString();
+ ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+
+ var i, len;
+ for (i = 0, len = title.length; i < len; ++i) {
+ ctx.fillText(title[i], pt.x, pt.y);
+ pt.y += titleFontSize + titleSpacing; // Line Height and spacing
+
+ if (i + 1 === title.length) {
+ pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
+ }
+ }
+ }
+ },
+ drawBody: function(pt, vm, ctx, opacity) {
+ var bodyFontSize = vm.bodyFontSize;
+ var bodySpacing = vm.bodySpacing;
+ var body = vm.body;
+
+ ctx.textAlign = vm._bodyAlign;
+ ctx.textBaseline = 'top';
+
+ var bodyFontColor = helpers.color(vm.bodyFontColor);
+ var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString();
+ ctx.fillStyle = textColor;
+ ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+
+ // Before Body
+ var xLinePadding = 0;
+ var fillLineOfText = function(line) {
+ ctx.fillText(line, pt.x + xLinePadding, pt.y);
+ pt.y += bodyFontSize + bodySpacing;
+ };
+
+ // Before body lines
+ helpers.each(vm.beforeBody, fillLineOfText);
+
+ var drawColorBoxes = body.length > 1;
+ xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
+
+ // Draw body lines now
+ helpers.each(body, function(bodyItem, i) {
+ helpers.each(bodyItem.before, fillLineOfText);
+
+ helpers.each(bodyItem.lines, function(line) {
+ // Draw Legend-like boxes if needed
+ if (drawColorBoxes) {
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
+ ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
+ ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+ // Border
+ ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
+ ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+ // Inner square
+ ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
+ ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
+
+ ctx.fillStyle = textColor;
+ }
+
+ fillLineOfText(line);
+ });
+
+ helpers.each(bodyItem.after, fillLineOfText);
+ });
+
+ // Reset back to 0 for after body
+ xLinePadding = 0;
+
+ // After body lines
+ helpers.each(vm.afterBody, fillLineOfText);
+ pt.y -= bodySpacing; // Remove last body spacing
+ },
+ drawFooter: function(pt, vm, ctx, opacity) {
+ var footer = vm.footer;
+
+ if (footer.length) {
+ pt.y += vm.footerMarginTop;
+
+ ctx.textAlign = vm._footerAlign;
+ ctx.textBaseline = 'top';
+
+ var footerFontColor = helpers.color(vm.footerFontColor);
+ ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString();
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+
+ helpers.each(footer, function(line) {
+ ctx.fillText(line, pt.x, pt.y);
+ pt.y += vm.footerFontSize + vm.footerSpacing;
+ });
+ }
+ },
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ if (vm.opacity === 0) {
+ return;
+ }
+
+ var tooltipSize = this.getTooltipSize(vm);
+ var pt = {
+ x: vm.x,
+ y: vm.y
+ };
+
+ // IE11/Edge does not like very small opacities, so snap to 0
+ var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
+
+ if (this._options.enabled) {
+ // Draw Background
+ var bgColor = helpers.color(vm.backgroundColor);
+ ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
+ helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
+ ctx.fill();
+
+ // Draw Caret
+ this.drawCaret(pt, tooltipSize, opacity);
+
+ // Draw Title, Body, and Footer
+ pt.x += vm.xPadding;
+ pt.y += vm.yPadding;
+
+ // Titles
+ this.drawTitle(pt, vm, ctx, opacity);
+
+ // Body
+ this.drawBody(pt, vm, ctx, opacity);
+
+ // Footer
+ this.drawFooter(pt, vm, ctx, opacity);
+ }
+ }
+ });
+};
+
+},{}],35:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers,
+ globalOpts = Chart.defaults.global;
+
+ globalOpts.elements.arc = {
+ backgroundColor: globalOpts.defaultColor,
+ borderColor: '#fff',
+ borderWidth: 2
+ };
+
+ Chart.elements.Arc = Chart.Element.extend({
+ inLabelRange: function(mouseX) {
+ var vm = this._view;
+
+ if (vm) {
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
+ }
+ return false;
+ },
+ inRange: function(chartX, chartY) {
+ var vm = this._view;
+
+ if (vm) {
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {
+ x: chartX,
+ y: chartY
+ }),
+ angle = pointRelativePosition.angle,
+ distance = pointRelativePosition.distance;
+
+ // Sanitise angle range
+ var startAngle = vm.startAngle;
+ var endAngle = vm.endAngle;
+ while (endAngle < startAngle) {
+ endAngle += 2.0 * Math.PI;
+ }
+ while (angle > endAngle) {
+ angle -= 2.0 * Math.PI;
+ }
+ while (angle < startAngle) {
+ angle += 2.0 * Math.PI;
+ }
+
+ // Check if within the range of the open/close angle
+ var betweenAngles = (angle >= startAngle && angle <= endAngle),
+ withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ }
+ return false;
+ },
+ tooltipPosition: function() {
+ var vm = this._view;
+
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
+ rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
+ return {
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw: function() {
+
+ var ctx = this._chart.ctx,
+ vm = this._view,
+ sA = vm.startAngle,
+ eA = vm.endAngle;
+
+ ctx.beginPath();
+
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = vm.borderWidth;
+
+ ctx.fillStyle = vm.backgroundColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (vm.borderWidth) {
+ ctx.stroke();
+ }
+ }
+ });
+};
+
+},{}],36:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ var globalDefaults = Chart.defaults.global;
+
+ Chart.defaults.global.elements.line = {
+ tension: 0.4,
+ backgroundColor: globalDefaults.defaultColor,
+ borderWidth: 3,
+ borderColor: globalDefaults.defaultColor,
+ borderCapStyle: 'butt',
+ borderDash: [],
+ borderDashOffset: 0.0,
+ borderJoinStyle: 'miter',
+ capBezierPoints: true,
+ fill: true // do we fill in the area between the line and its base axis
+ };
+
+ Chart.elements.Line = Chart.Element.extend({
+ draw: function() {
+ var me = this;
+ var vm = me._view;
+ var spanGaps = vm.spanGaps;
+ var scaleZero = vm.scaleZero;
+ var loop = me._loop;
+
+ var ctx = me._chart.ctx;
+ ctx.save();
+
+ // Helper function to draw a line to a point
+ function lineToPoint(previousPoint, point) {
+ var pointVM = point._view;
+ if (point._view.steppedLine === true) {
+ ctx.lineTo(pointVM.x, previousPoint._view.y);
+ ctx.lineTo(pointVM.x, pointVM.y);
+ } else if (point._view.tension === 0) {
+ ctx.lineTo(pointVM.x, pointVM.y);
+ } else {
+ ctx.bezierCurveTo(
+ previousPoint._view.controlPointNextX,
+ previousPoint._view.controlPointNextY,
+ pointVM.controlPointPreviousX,
+ pointVM.controlPointPreviousY,
+ pointVM.x,
+ pointVM.y
+ );
+ }
+ }
+
+ var points = me._children.slice(); // clone array
+ var lastDrawnIndex = -1;
+
+ // If we are looping, adding the first point again
+ if (loop && points.length) {
+ points.push(points[0]);
+ }
+
+ var index, current, previous, currentVM;
+
+ // Fill Line
+ if (points.length && vm.fill) {
+ ctx.beginPath();
+
+ for (index = 0; index < points.length; ++index) {
+ current = points[index];
+ previous = helpers.previousItem(points, index);
+ currentVM = current._view;
+
+ // First point moves to it's starting position no matter what
+ if (index === 0) {
+ if (loop) {
+ ctx.moveTo(scaleZero.x, scaleZero.y);
+ } else {
+ ctx.moveTo(currentVM.x, scaleZero);
+ }
+
+ if (!currentVM.skip) {
+ lastDrawnIndex = index;
+ ctx.lineTo(currentVM.x, currentVM.y);
+ }
+ } else {
+ previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
+
+ if (currentVM.skip) {
+ // Only do this if this is the first point that is skipped
+ if (!spanGaps && lastDrawnIndex === (index - 1)) {
+ if (loop) {
+ ctx.lineTo(scaleZero.x, scaleZero.y);
+ } else {
+ ctx.lineTo(previous._view.x, scaleZero);
+ }
+ }
+ } else {
+ if (lastDrawnIndex !== (index - 1)) {
+ // There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case.
+ // If the first data point is NaN, then there is no real gap to skip
+ if (spanGaps && lastDrawnIndex !== -1) {
+ // We are spanning the gap, so simple draw a line to this point
+ lineToPoint(previous, current);
+ } else if (loop) {
+ ctx.lineTo(currentVM.x, currentVM.y);
+ } else {
+ ctx.lineTo(currentVM.x, scaleZero);
+ ctx.lineTo(currentVM.x, currentVM.y);
+ }
+ } else {
+ // Line to next point
+ lineToPoint(previous, current);
+ }
+ lastDrawnIndex = index;
+ }
+ }
+ }
+
+ if (!loop && lastDrawnIndex !== -1) {
+ ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero);
+ }
+
+ ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
+ ctx.closePath();
+ ctx.fill();
+ }
+
+ // Stroke Line Options
+ var globalOptionLineElements = globalDefaults.elements.line;
+ ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
+
+ // IE 9 and 10 do not support line dash
+ if (ctx.setLineDash) {
+ ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
+ }
+
+ ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
+ ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
+ ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
+ ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
+
+ // Stroke Line
+ ctx.beginPath();
+ lastDrawnIndex = -1;
+
+ for (index = 0; index < points.length; ++index) {
+ current = points[index];
+ previous = helpers.previousItem(points, index);
+ currentVM = current._view;
+
+ // First point moves to it's starting position no matter what
+ if (index === 0) {
+ if (!currentVM.skip) {
+ ctx.moveTo(currentVM.x, currentVM.y);
+ lastDrawnIndex = index;
+ }
+ } else {
+ previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
+
+ if (!currentVM.skip) {
+ if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
+ // There was a gap and this is the first point after the gap
+ ctx.moveTo(currentVM.x, currentVM.y);
+ } else {
+ // Line to next point
+ lineToPoint(previous, current);
+ }
+ lastDrawnIndex = index;
+ }
+ }
+ }
+
+ ctx.stroke();
+ ctx.restore();
+ }
+ });
+};
+
+},{}],37:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers,
+ globalOpts = Chart.defaults.global,
+ defaultColor = globalOpts.defaultColor;
+
+ globalOpts.elements.point = {
+ radius: 3,
+ pointStyle: 'circle',
+ backgroundColor: defaultColor,
+ borderWidth: 1,
+ borderColor: defaultColor,
+ // Hover
+ hitRadius: 1,
+ hoverRadius: 4,
+ hoverBorderWidth: 1
+ };
+
+ Chart.elements.Point = Chart.Element.extend({
+ inRange: function(mouseX, mouseY) {
+ var vm = this._view;
+ return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
+ },
+ inLabelRange: function(mouseX) {
+ var vm = this._view;
+ return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+ },
+ tooltipPosition: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y,
+ padding: vm.radius + vm.borderWidth
+ };
+ },
+ draw: function() {
+ var vm = this._view;
+ var ctx = this._chart.ctx;
+ var pointStyle = vm.pointStyle;
+ var radius = vm.radius;
+ var x = vm.x;
+ var y = vm.y;
+
+ if (vm.skip) {
+ return;
+ }
+
+ ctx.strokeStyle = vm.borderColor || defaultColor;
+ ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
+ ctx.fillStyle = vm.backgroundColor || defaultColor;
+
+ Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
+ }
+ });
+};
+
+},{}],38:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var globalOpts = Chart.defaults.global;
+
+ globalOpts.elements.rectangle = {
+ backgroundColor: globalOpts.defaultColor,
+ borderWidth: 0,
+ borderColor: globalOpts.defaultColor,
+ borderSkipped: 'bottom'
+ };
+
+ Chart.elements.Rectangle = Chart.Element.extend({
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ var halfWidth = vm.width / 2,
+ leftX = vm.x - halfWidth,
+ rightX = vm.x + halfWidth,
+ top = vm.base - (vm.base - vm.y),
+ halfStroke = vm.borderWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (vm.borderWidth) {
+ leftX += halfStroke;
+ rightX -= halfStroke;
+ top += halfStroke;
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = vm.backgroundColor;
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = vm.borderWidth;
+
+ // Corner points, from bottom-left to bottom-right clockwise
+ // | 1 2 |
+ // | 0 3 |
+ var corners = [
+ [leftX, vm.base],
+ [leftX, top],
+ [rightX, top],
+ [rightX, vm.base]
+ ];
+
+ // Find first (starting) corner with fallback to 'bottom'
+ var borders = ['bottom', 'left', 'top', 'right'];
+ var startCorner = borders.indexOf(vm.borderSkipped, 0);
+ if (startCorner === -1) {
+ startCorner = 0;
+ }
+
+ function cornerAt(index) {
+ return corners[(startCorner + index) % 4];
+ }
+
+ // Draw rectangle from 'startCorner'
+ ctx.moveTo.apply(ctx, cornerAt(0));
+ for (var i = 1; i < 4; i++) {
+ ctx.lineTo.apply(ctx, cornerAt(i));
+ }
+
+ ctx.fill();
+ if (vm.borderWidth) {
+ ctx.stroke();
+ }
+ },
+ height: function() {
+ var vm = this._view;
+ return vm.base - vm.y;
+ },
+ inRange: function(mouseX, mouseY) {
+ var vm = this._view;
+ return vm ?
+ (vm.y < vm.base ?
+ (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) :
+ (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
+ false;
+ },
+ inLabelRange: function(mouseX) {
+ var vm = this._view;
+ return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false;
+ },
+ tooltipPosition: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y
+ };
+ }
+ });
+
+};
+
+},{}],39:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ // Default config for a category scale
+ var defaultConfig = {
+ position: 'bottom'
+ };
+
+ var DatasetScale = Chart.Scale.extend({
+ /**
+ * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose
+ * else fall back to data.labels
+ * @private
+ */
+ getLabels: function() {
+ var data = this.chart.data;
+ return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
+ },
+ // Implement this so that
+ determineDataLimits: function() {
+ var me = this;
+ var labels = me.getLabels();
+ me.minIndex = 0;
+ me.maxIndex = labels.length - 1;
+ var findIndex;
+
+ if (me.options.ticks.min !== undefined) {
+ // user specified min value
+ findIndex = helpers.indexOf(labels, me.options.ticks.min);
+ me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
+ }
+
+ if (me.options.ticks.max !== undefined) {
+ // user specified max value
+ findIndex = helpers.indexOf(labels, me.options.ticks.max);
+ me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
+ }
+
+ me.min = labels[me.minIndex];
+ me.max = labels[me.maxIndex];
+ },
+
+ buildTicks: function() {
+ var me = this;
+ var labels = me.getLabels();
+ // If we are viewing some subset of labels, slice the original array
+ me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
+ },
+
+ getLabelForIndex: function(index, datasetIndex) {
+ var me = this;
+ var data = me.chart.data;
+ var isHorizontal = me.isHorizontal();
+
+ if ((data.xLabels && isHorizontal) || (data.yLabels && !isHorizontal)) {
+ return me.getRightValue(data.datasets[datasetIndex].data[index]);
+ }
+ return me.ticks[index];
+ },
+
+ // Used to get data value locations. Value can either be an index or a numerical value
+ getPixelForValue: function(value, index, datasetIndex, includeOffset) {
+ var me = this;
+ // 1 is added because we need the length but we have the indexes
+ var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+
+ if (value !== undefined && isNaN(index)) {
+ var labels = me.getLabels();
+ var idx = labels.indexOf(value);
+ index = idx !== -1 ? idx : index;
+ }
+
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var valueWidth = innerWidth / offsetAmt;
+ var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft;
+
+ if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
+ widthOffset += (valueWidth / 2);
+ }
+
+ return me.left + Math.round(widthOffset);
+ }
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
+ var valueHeight = innerHeight / offsetAmt;
+ var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop;
+
+ if (me.options.gridLines.offsetGridLines && includeOffset) {
+ heightOffset += (valueHeight / 2);
+ }
+
+ return me.top + Math.round(heightOffset);
+ },
+ getPixelForTick: function(index, includeOffset) {
+ return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var value;
+ var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+ var horz = me.isHorizontal();
+ var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
+ var valueDimension = innerDimension / offsetAmt;
+
+ pixel -= horz ? me.left : me.top;
+
+ if (me.options.gridLines.offsetGridLines) {
+ pixel -= (valueDimension / 2);
+ }
+ pixel -= horz ? me.paddingLeft : me.paddingTop;
+
+ if (pixel <= 0) {
+ value = 0;
+ } else {
+ value = Math.round(pixel / valueDimension);
+ }
+
+ return value;
+ },
+ getBasePixel: function() {
+ return this.bottom;
+ }
+ });
+
+ Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
+
+};
+
+},{}],40:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ var defaultConfig = {
+ position: 'left',
+ ticks: {
+ callback: function(tickValue, index, ticks) {
+ // If we have lots of ticks, don't use the ones
+ var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
+
+ // If we have a number like 2.5 as the delta, figure out how many decimal places we need
+ if (Math.abs(delta) > 1) {
+ if (tickValue !== Math.floor(tickValue)) {
+ // not an integer
+ delta = tickValue - Math.floor(tickValue);
+ }
+ }
+
+ var logDelta = helpers.log10(Math.abs(delta));
+ var tickString = '';
+
+ if (tickValue !== 0) {
+ var numDecimal = -1 * Math.floor(logDelta);
+ numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
+ tickString = tickValue.toFixed(numDecimal);
+ } else {
+ tickString = '0'; // never show decimal places for 0
+ }
+
+ return tickString;
+ }
+ }
+ };
+
+ var LinearScale = Chart.LinearScaleBase.extend({
+ determineDataLimits: function() {
+ var me = this;
+ var opts = me.options;
+ var chart = me.chart;
+ var data = chart.data;
+ var datasets = data.datasets;
+ var isHorizontal = me.isHorizontal();
+
+ function IDMatches(meta) {
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
+ }
+
+ // First Calculate the range
+ me.min = null;
+ me.max = null;
+
+ if (opts.stacked) {
+ var valuesPerType = {};
+
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (valuesPerType[meta.type] === undefined) {
+ valuesPerType[meta.type] = {
+ positiveValues: [],
+ negativeValues: []
+ };
+ }
+
+ // Store these per type
+ var positiveValues = valuesPerType[meta.type].positiveValues;
+ var negativeValues = valuesPerType[meta.type].negativeValues;
+
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ positiveValues[index] = positiveValues[index] || 0;
+ negativeValues[index] = negativeValues[index] || 0;
+
+ if (opts.relativePoints) {
+ positiveValues[index] = 100;
+ } else if (value < 0) {
+ negativeValues[index] += value;
+ } else {
+ positiveValues[index] += value;
+ }
+ });
+ }
+ });
+
+ helpers.each(valuesPerType, function(valuesForType) {
+ var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
+ var minVal = helpers.min(values);
+ var maxVal = helpers.max(values);
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
+ });
+
+ } else {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ if (me.min === null) {
+ me.min = value;
+ } else if (value < me.min) {
+ me.min = value;
+ }
+
+ if (me.max === null) {
+ me.max = value;
+ } else if (value > me.max) {
+ me.max = value;
+ }
+ });
+ }
+ });
+ }
+
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
+ this.handleTickRangeOptions();
+ },
+ getTickLimit: function() {
+ var maxTicks;
+ var me = this;
+ var tickOpts = me.options.ticks;
+
+ if (me.isHorizontal()) {
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
+ } else {
+ // The factor of 2 used to scale the font size has been experimentally determined.
+ var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize);
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
+ }
+
+ return maxTicks;
+ },
+ // Called after the ticks are built. We need
+ handleDirectionalChanges: function() {
+ if (!this.isHorizontal()) {
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
+ this.ticks.reverse();
+ }
+ },
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ // Utils
+ getPixelForValue: function(value) {
+ // This must be called after fit has been run so that
+ // this.left, this.top, this.right, and this.bottom have been defined
+ var me = this;
+ var paddingLeft = me.paddingLeft;
+ var paddingBottom = me.paddingBottom;
+ var start = me.start;
+
+ var rightValue = +me.getRightValue(value);
+ var pixel;
+ var innerDimension;
+ var range = me.end - start;
+
+ if (me.isHorizontal()) {
+ innerDimension = me.width - (paddingLeft + me.paddingRight);
+ pixel = me.left + (innerDimension / range * (rightValue - start));
+ return Math.round(pixel + paddingLeft);
+ }
+ innerDimension = me.height - (me.paddingTop + paddingBottom);
+ pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start));
+ return Math.round(pixel);
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var isHorizontal = me.isHorizontal();
+ var paddingLeft = me.paddingLeft;
+ var paddingBottom = me.paddingBottom;
+ var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom);
+ var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension;
+ return me.start + ((me.end - me.start) * offset);
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.ticksAsNumbers[index]);
+ }
+ });
+ Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
+
+};
+
+},{}],41:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers,
+ noop = helpers.noop;
+
+ Chart.LinearScaleBase = Chart.Scale.extend({
+ handleTickRangeOptions: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
+ // axis, they can manually override it
+ if (tickOpts.beginAtZero) {
+ var minSign = helpers.sign(me.min);
+ var maxSign = helpers.sign(me.max);
+
+ if (minSign < 0 && maxSign < 0) {
+ // move the top up to 0
+ me.max = 0;
+ } else if (minSign > 0 && maxSign > 0) {
+ // move the botttom down to 0
+ me.min = 0;
+ }
+ }
+
+ if (tickOpts.min !== undefined) {
+ me.min = tickOpts.min;
+ } else if (tickOpts.suggestedMin !== undefined) {
+ me.min = Math.min(me.min, tickOpts.suggestedMin);
+ }
+
+ if (tickOpts.max !== undefined) {
+ me.max = tickOpts.max;
+ } else if (tickOpts.suggestedMax !== undefined) {
+ me.max = Math.max(me.max, tickOpts.suggestedMax);
+ }
+
+ if (me.min === me.max) {
+ me.max++;
+
+ if (!tickOpts.beginAtZero) {
+ me.min--;
+ }
+ }
+ },
+ getTickLimit: noop,
+ handleDirectionalChanges: noop,
+
+ buildTicks: function() {
+ var me = this;
+ var opts = me.options;
+ var ticks = me.ticks = [];
+ var tickOpts = opts.ticks;
+ var getValueOrDefault = helpers.getValueOrDefault;
+
+ // Figure out what the max number of ticks we can support it is based on the size of
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
+ // the graph
+
+ var maxTicks = me.getTickLimit();
+
+ // Make sure we always have at least 2 ticks
+ maxTicks = Math.max(2, maxTicks);
+
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
+ // for details.
+
+ var spacing;
+ var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0);
+ if (fixedStepSizeSet) {
+ spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize);
+ } else {
+ var niceRange = helpers.niceNum(me.max - me.min, false);
+ spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
+ }
+ var niceMin = Math.floor(me.min / spacing) * spacing;
+ var niceMax = Math.ceil(me.max / spacing) * spacing;
+ var numSpaces = (niceMax - niceMin) / spacing;
+
+ // If very close to our rounded value, use it.
+ if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
+ numSpaces = Math.round(numSpaces);
+ } else {
+ numSpaces = Math.ceil(numSpaces);
+ }
+
+ // Put the values into the ticks array
+ ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin);
+ for (var j = 1; j < numSpaces; ++j) {
+ ticks.push(niceMin + (j * spacing));
+ }
+ ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax);
+
+ me.handleDirectionalChanges();
+
+ // At this point, we need to update our max and min given the tick values since we have expanded the
+ // range of the scale
+ me.max = helpers.max(ticks);
+ me.min = helpers.min(ticks);
+
+ if (tickOpts.reverse) {
+ ticks.reverse();
+
+ me.start = me.max;
+ me.end = me.min;
+ } else {
+ me.start = me.min;
+ me.end = me.max;
+ }
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+ me.ticksAsNumbers = me.ticks.slice();
+ me.zeroLineIndex = me.ticks.indexOf(0);
+
+ Chart.Scale.prototype.convertTicksToLabels.call(me);
+ }
+ });
+};
+
+},{}],42:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+
+ var defaultConfig = {
+ position: 'left',
+
+ // label settings
+ ticks: {
+ callback: function(value, index, arr) {
+ var remain = value / (Math.pow(10, Math.floor(helpers.log10(value))));
+
+ if (value === 0) {
+ return '0';
+ } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
+ return value.toExponential();
+ }
+ return '';
+ }
+ }
+ };
+
+ var LogarithmicScale = Chart.Scale.extend({
+ determineDataLimits: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ var chart = me.chart;
+ var data = chart.data;
+ var datasets = data.datasets;
+ var getValueOrDefault = helpers.getValueOrDefault;
+ var isHorizontal = me.isHorizontal();
+ function IDMatches(meta) {
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
+ }
+
+ // Calculate Range
+ me.min = null;
+ me.max = null;
+ me.minNotZero = null;
+
+ if (opts.stacked) {
+ var valuesPerType = {};
+
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ if (valuesPerType[meta.type] === undefined) {
+ valuesPerType[meta.type] = [];
+ }
+
+ helpers.each(dataset.data, function(rawValue, index) {
+ var values = valuesPerType[meta.type];
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ values[index] = values[index] || 0;
+
+ if (opts.relativePoints) {
+ values[index] = 100;
+ } else {
+ // Don't need to split positive and negative since the log scale can't handle a 0 crossing
+ values[index] += value;
+ }
+ });
+ }
+ });
+
+ helpers.each(valuesPerType, function(valuesForType) {
+ var minVal = helpers.min(valuesForType);
+ var maxVal = helpers.max(valuesForType);
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
+ });
+
+ } else {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ if (me.min === null) {
+ me.min = value;
+ } else if (value < me.min) {
+ me.min = value;
+ }
+
+ if (me.max === null) {
+ me.max = value;
+ } else if (value > me.max) {
+ me.max = value;
+ }
+
+ if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
+ me.minNotZero = value;
+ }
+ });
+ }
+ });
+ }
+
+ me.min = getValueOrDefault(tickOpts.min, me.min);
+ me.max = getValueOrDefault(tickOpts.max, me.max);
+
+ if (me.min === me.max) {
+ if (me.min !== 0 && me.min !== null) {
+ me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
+ me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
+ } else {
+ me.min = 1;
+ me.max = 10;
+ }
+ }
+ },
+ buildTicks: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ var getValueOrDefault = helpers.getValueOrDefault;
+
+ // Reset the ticks array. Later on, we will draw a grid line at these positions
+ // The array simply contains the numerical value of the spots where ticks will be
+ var ticks = me.ticks = [];
+
+ // Figure out what the max number of ticks we can support it is based on the size of
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
+ // the graph
+
+ var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min))));
+
+ while (tickVal < me.max) {
+ ticks.push(tickVal);
+
+ var exp;
+ var significand;
+
+ if (tickVal === 0) {
+ exp = Math.floor(helpers.log10(me.minNotZero));
+ significand = Math.round(me.minNotZero / Math.pow(10, exp));
+ } else {
+ exp = Math.floor(helpers.log10(tickVal));
+ significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
+ }
+
+ if (significand === 10) {
+ significand = 1;
+ ++exp;
+ }
+
+ tickVal = significand * Math.pow(10, exp);
+ }
+
+ var lastTick = getValueOrDefault(tickOpts.max, tickVal);
+ ticks.push(lastTick);
+
+ if (!me.isHorizontal()) {
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
+ ticks.reverse();
+ }
+
+ // At this point, we need to update our max and min given the tick values since we have expanded the
+ // range of the scale
+ me.max = helpers.max(ticks);
+ me.min = helpers.min(ticks);
+
+ if (tickOpts.reverse) {
+ ticks.reverse();
+
+ me.start = me.max;
+ me.end = me.min;
+ } else {
+ me.start = me.min;
+ me.end = me.max;
+ }
+ },
+ convertTicksToLabels: function() {
+ this.tickValues = this.ticks.slice();
+
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
+ },
+ // Get the correct tooltip label
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.tickValues[index]);
+ },
+ getPixelForValue: function(value) {
+ var me = this;
+ var innerDimension;
+ var pixel;
+
+ var start = me.start;
+ var newVal = +me.getRightValue(value);
+ var range;
+ var paddingTop = me.paddingTop;
+ var paddingBottom = me.paddingBottom;
+ var paddingLeft = me.paddingLeft;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+
+ if (me.isHorizontal()) {
+ range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
+ if (newVal === 0) {
+ pixel = me.left + paddingLeft;
+ } else {
+ innerDimension = me.width - (paddingLeft + me.paddingRight);
+ pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
+ pixel += paddingLeft;
+ }
+ } else {
+ // Bottom - top since pixels increase downard on a screen
+ innerDimension = me.height - (paddingTop + paddingBottom);
+ if (start === 0 && !tickOpts.reverse) {
+ range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
+ if (newVal === start) {
+ pixel = me.bottom - paddingBottom;
+ } else if (newVal === me.minNotZero) {
+ pixel = me.bottom - paddingBottom - innerDimension * 0.02;
+ } else {
+ pixel = me.bottom - paddingBottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
+ }
+ } else if (me.end === 0 && tickOpts.reverse) {
+ range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
+ if (newVal === me.end) {
+ pixel = me.top + paddingTop;
+ } else if (newVal === me.minNotZero) {
+ pixel = me.top + paddingTop + innerDimension * 0.02;
+ } else {
+ pixel = me.top + paddingTop + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
+ }
+ } else {
+ range = helpers.log10(me.end) - helpers.log10(start);
+ innerDimension = me.height - (paddingTop + paddingBottom);
+ pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
+ }
+ }
+ return pixel;
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var range = helpers.log10(me.end) - helpers.log10(me.start);
+ var value, innerDimension;
+
+ if (me.isHorizontal()) {
+ innerDimension = me.width - (me.paddingLeft + me.paddingRight);
+ value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension);
+ } else { // todo: if start === 0
+ innerDimension = me.height - (me.paddingTop + me.paddingBottom);
+ value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start;
+ }
+ return value;
+ }
+ });
+ Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
+
+};
+
+},{}],43:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ var globalDefaults = Chart.defaults.global;
+
+ var defaultConfig = {
+ display: true,
+
+ // Boolean - Whether to animate scaling the chart from the centre
+ animate: true,
+ lineArc: false,
+ position: 'chartArea',
+
+ angleLines: {
+ display: true,
+ color: 'rgba(0, 0, 0, 0.1)',
+ lineWidth: 1
+ },
+
+ // label settings
+ ticks: {
+ // Boolean - Show a backdrop to the scale label
+ showLabelBackdrop: true,
+
+ // String - The colour of the label backdrop
+ backdropColor: 'rgba(255,255,255,0.75)',
+
+ // Number - The backdrop padding above & below the label in pixels
+ backdropPaddingY: 2,
+
+ // Number - The backdrop padding to the side of the label in pixels
+ backdropPaddingX: 2
+ },
+
+ pointLabels: {
+ // Number - Point label font size in pixels
+ fontSize: 10,
+
+ // Function - Used to convert point labels
+ callback: function(label) {
+ return label;
+ }
+ }
+ };
+
+ var LinearRadialScale = Chart.LinearScaleBase.extend({
+ getValueCount: function() {
+ return this.chart.data.labels.length;
+ },
+ setDimensions: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ // Set the unconstrained dimension before label rotation
+ me.width = me.maxWidth;
+ me.height = me.maxHeight;
+ me.xCenter = Math.round(me.width / 2);
+ me.yCenter = Math.round(me.height / 2);
+
+ var minSize = helpers.min([me.height, me.width]);
+ var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
+ },
+ determineDataLimits: function() {
+ var me = this;
+ var chart = me.chart;
+ me.min = null;
+ me.max = null;
+
+
+ helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
+ if (chart.isDatasetVisible(datasetIndex)) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ if (me.min === null) {
+ me.min = value;
+ } else if (value < me.min) {
+ me.min = value;
+ }
+
+ if (me.max === null) {
+ me.max = value;
+ } else if (value > me.max) {
+ me.max = value;
+ }
+ });
+ }
+ });
+
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
+ me.handleTickRangeOptions();
+ },
+ getTickLimit: function() {
+ var tickOpts = this.options.ticks;
+ var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+ Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
+
+ // Point labels
+ me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
+ },
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ fit: function() {
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+ var pointLabels = this.options.pointLabels;
+ var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
+ var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
+ var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
+ var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
+ pointPosition,
+ i,
+ textWidth,
+ halfTextWidth,
+ furthestRight = this.width,
+ furthestRightIndex,
+ furthestRightAngle,
+ furthestLeft = 0,
+ furthestLeftIndex,
+ furthestLeftAngle,
+ xProtrusionLeft,
+ xProtrusionRight,
+ radiusReductionRight,
+ radiusReductionLeft;
+ this.ctx.font = pointLabeFont;
+
+ for (i = 0; i < this.getValueCount(); i++) {
+ // 5px to space the text slightly out - similar to what we do in the draw function.
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
+ textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
+
+ // Add quarter circle to make degree 0 mean top of circle
+ var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
+ var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
+
+ if (angle === 0 || angle === 180) {
+ // At angle 0 and 180, we're at exactly the top/bottom
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
+ // w/left and right text sizes
+ halfTextWidth = textWidth / 2;
+ if (pointPosition.x + halfTextWidth > furthestRight) {
+ furthestRight = pointPosition.x + halfTextWidth;
+ furthestRightIndex = i;
+ }
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - halfTextWidth;
+ furthestLeftIndex = i;
+ }
+ } else if (angle < 180) {
+ // Less than half the values means we'll left align the text
+ if (pointPosition.x + textWidth > furthestRight) {
+ furthestRight = pointPosition.x + textWidth;
+ furthestRightIndex = i;
+ }
+ // More than half the values means we'll right align the text
+ } else if (pointPosition.x - textWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - textWidth;
+ furthestLeftIndex = i;
+ }
+ }
+
+ xProtrusionLeft = furthestLeft;
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
+
+ // Ensure we actually need to reduce the size of the chart
+ radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+ radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+ this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+ },
+ setCenterPoint: function(leftMovement, rightMovement) {
+ var me = this;
+ var maxRight = me.width - rightMovement - me.drawingArea,
+ maxLeft = leftMovement + me.drawingArea;
+
+ me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
+ // Always vertically in the centre as the text height doesn't change
+ me.yCenter = Math.round((me.height / 2) + me.top);
+ },
+
+ getIndexAngle: function(index) {
+ var angleMultiplier = (Math.PI * 2) / this.getValueCount();
+ var startAngle = this.chart.options && this.chart.options.startAngle ?
+ this.chart.options.startAngle :
+ 0;
+
+ var startAngleRadians = startAngle * Math.PI * 2 / 360;
+
+ // Start from the top instead of right, so remove a quarter of the circle
+ return index * angleMultiplier - (Math.PI / 2) + startAngleRadians;
+ },
+ getDistanceFromCenterForValue: function(value) {
+ var me = this;
+
+ if (value === null) {
+ return 0; // null always in center
+ }
+
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = me.drawingArea / (me.max - me.min);
+ if (me.options.reverse) {
+ return (me.max - value) * scalingFactor;
+ }
+ return (value - me.min) * scalingFactor;
+ },
+ getPointPosition: function(index, distanceFromCenter) {
+ var me = this;
+ var thisAngle = me.getIndexAngle(index);
+ return {
+ x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
+ y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
+ };
+ },
+ getPointPositionForValue: function(index, value) {
+ return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
+ },
+
+ getBasePosition: function() {
+ var me = this;
+ var min = me.min;
+ var max = me.max;
+
+ return me.getPointPositionForValue(0,
+ me.beginAtZero? 0:
+ min < 0 && max < 0? max :
+ min > 0 && max > 0? min :
+ 0);
+ },
+
+ draw: function() {
+ var me = this;
+ var opts = me.options;
+ var gridLineOpts = opts.gridLines;
+ var tickOpts = opts.ticks;
+ var angleLineOpts = opts.angleLines;
+ var pointLabelOpts = opts.pointLabels;
+ var getValueOrDefault = helpers.getValueOrDefault;
+
+ if (opts.display) {
+ var ctx = me.ctx;
+
+ // Tick Font
+ var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
+ var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
+
+ helpers.each(me.ticks, function(label, index) {
+ // Don't draw a centre value (if it is minimum)
+ if (index > 0 || opts.reverse) {
+ var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+ var yHeight = me.yCenter - yCenterOffset;
+
+ // Draw circular lines around the scale
+ if (gridLineOpts.display && index !== 0) {
+ ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
+ ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
+
+ if (opts.lineArc) {
+ // Draw circular arcs between the points
+ ctx.beginPath();
+ ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.stroke();
+ } else {
+ // Draw straight lines connecting each index
+ ctx.beginPath();
+ for (var i = 0; i < me.getValueCount(); i++) {
+ var pointPosition = me.getPointPosition(i, yCenterOffset);
+ if (i === 0) {
+ ctx.moveTo(pointPosition.x, pointPosition.y);
+ } else {
+ ctx.lineTo(pointPosition.x, pointPosition.y);
+ }
+ }
+ ctx.closePath();
+ ctx.stroke();
+ }
+ }
+
+ if (tickOpts.display) {
+ var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
+ ctx.font = tickLabelFont;
+
+ if (tickOpts.showLabelBackdrop) {
+ var labelWidth = ctx.measureText(label).width;
+ ctx.fillStyle = tickOpts.backdropColor;
+ ctx.fillRect(
+ me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX,
+ yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY,
+ labelWidth + tickOpts.backdropPaddingX * 2,
+ tickFontSize + tickOpts.backdropPaddingY * 2
+ );
+ }
+
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillStyle = tickFontColor;
+ ctx.fillText(label, me.xCenter, yHeight);
+ }
+ }
+ });
+
+ if (!opts.lineArc) {
+ ctx.lineWidth = angleLineOpts.lineWidth;
+ ctx.strokeStyle = angleLineOpts.color;
+
+ var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max);
+
+ // Point Label Font
+ var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize);
+ var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
+ var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
+ var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
+
+ for (var i = me.getValueCount() - 1; i >= 0; i--) {
+ if (angleLineOpts.display) {
+ var outerPosition = me.getPointPosition(i, outerDistance);
+ ctx.beginPath();
+ ctx.moveTo(me.xCenter, me.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = me.getPointPosition(i, outerDistance + 5);
+
+ // Keep this in loop since we may support array properties here
+ var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
+ ctx.font = pointLabeFont;
+ ctx.fillStyle = pointLabelFontColor;
+
+ var pointLabels = me.pointLabels;
+
+ // Add quarter circle to make degree 0 mean top of circle
+ var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
+ var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
+
+ if (angle === 0 || angle === 180) {
+ ctx.textAlign = 'center';
+ } else if (angle < 180) {
+ ctx.textAlign = 'left';
+ } else {
+ ctx.textAlign = 'right';
+ }
+
+ // Set the correct text baseline based on outer positioning
+ if (angle === 90 || angle === 270) {
+ ctx.textBaseline = 'middle';
+ } else if (angle > 270 || angle < 90) {
+ ctx.textBaseline = 'bottom';
+ } else {
+ ctx.textBaseline = 'top';
+ }
+
+ ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
+ }
+ }
+ }
+ }
+ });
+ Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
+
+};
+
+},{}],44:[function(require,module,exports){
+/* global window: false */
+'use strict';
+
+var moment = require(1);
+moment = typeof(moment) === 'function' ? moment : window.moment;
+
+module.exports = function(Chart) {
+
+ var helpers = Chart.helpers;
+ var time = {
+ units: [{
+ name: 'millisecond',
+ steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
+ }, {
+ name: 'second',
+ steps: [1, 2, 5, 10, 30]
+ }, {
+ name: 'minute',
+ steps: [1, 2, 5, 10, 30]
+ }, {
+ name: 'hour',
+ steps: [1, 2, 3, 6, 12]
+ }, {
+ name: 'day',
+ steps: [1, 2, 5]
+ }, {
+ name: 'week',
+ maxStep: 4
+ }, {
+ name: 'month',
+ maxStep: 3
+ }, {
+ name: 'quarter',
+ maxStep: 4
+ }, {
+ name: 'year',
+ maxStep: false
+ }]
+ };
+
+ var defaultConfig = {
+ position: 'bottom',
+
+ time: {
+ parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
+ format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
+ unit: false, // false == automatic or override with week, month, year, etc.
+ round: false, // none, or override with week, month, year, etc.
+ displayFormat: false, // DEPRECATED
+ isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
+ minUnit: 'millisecond',
+
+ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
+ displayFormats: {
+ millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
+ second: 'h:mm:ss a', // 11:20:01 AM
+ minute: 'h:mm:ss a', // 11:20:01 AM
+ hour: 'MMM D, hA', // Sept 4, 5PM
+ day: 'll', // Sep 4 2015
+ week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
+ month: 'MMM YYYY', // Sept 2015
+ quarter: '[Q]Q - YYYY', // Q3
+ year: 'YYYY' // 2015
+ }
+ },
+ ticks: {
+ autoSkip: false
+ }
+ };
+
+ var TimeScale = Chart.Scale.extend({
+ initialize: function() {
+ if (!moment) {
+ throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
+ }
+
+ Chart.Scale.prototype.initialize.call(this);
+ },
+ getLabelMoment: function(datasetIndex, index) {
+ if (datasetIndex === null || index === null) {
+ return null;
+ }
+
+ if (typeof this.labelMoments[datasetIndex] !== 'undefined') {
+ return this.labelMoments[datasetIndex][index];
+ }
+
+ return null;
+ },
+ getLabelDiff: function(datasetIndex, index) {
+ var me = this;
+ if (datasetIndex === null || index === null) {
+ return null;
+ }
+
+ if (me.labelDiffs === undefined) {
+ me.buildLabelDiffs();
+ }
+
+ if (typeof me.labelDiffs[datasetIndex] !== 'undefined') {
+ return me.labelDiffs[datasetIndex][index];
+ }
+
+ return null;
+ },
+ getMomentStartOf: function(tick) {
+ var me = this;
+ if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) {
+ return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday);
+ }
+ return tick.clone().startOf(me.tickUnit);
+ },
+ determineDataLimits: function() {
+ var me = this;
+ me.labelMoments = [];
+
+ // Only parse these once. If the dataset does not have data as x,y pairs, we will use
+ // these
+ var scaleLabelMoments = [];
+ if (me.chart.data.labels && me.chart.data.labels.length > 0) {
+ helpers.each(me.chart.data.labels, function(label) {
+ var labelMoment = me.parseTime(label);
+
+ if (labelMoment.isValid()) {
+ if (me.options.time.round) {
+ labelMoment.startOf(me.options.time.round);
+ }
+ scaleLabelMoments.push(labelMoment);
+ }
+ }, me);
+
+ me.firstTick = moment.min.call(me, scaleLabelMoments);
+ me.lastTick = moment.max.call(me, scaleLabelMoments);
+ } else {
+ me.firstTick = null;
+ me.lastTick = null;
+ }
+
+ helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
+ var momentsForDataset = [];
+ var datasetVisible = me.chart.isDatasetVisible(datasetIndex);
+
+ if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
+ helpers.each(dataset.data, function(value) {
+ var labelMoment = me.parseTime(me.getRightValue(value));
+
+ if (labelMoment.isValid()) {
+ if (me.options.time.round) {
+ labelMoment.startOf(me.options.time.round);
+ }
+ momentsForDataset.push(labelMoment);
+
+ if (datasetVisible) {
+ // May have gone outside the scale ranges, make sure we keep the first and last ticks updated
+ me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment;
+ me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment;
+ }
+ }
+ }, me);
+ } else {
+ // We have no labels. Use the ones from the scale
+ momentsForDataset = scaleLabelMoments;
+ }
+
+ me.labelMoments.push(momentsForDataset);
+ }, me);
+
+ // Set these after we've done all the data
+ if (me.options.time.min) {
+ me.firstTick = me.parseTime(me.options.time.min);
+ }
+
+ if (me.options.time.max) {
+ me.lastTick = me.parseTime(me.options.time.max);
+ }
+
+ // We will modify these, so clone for later
+ me.firstTick = (me.firstTick || moment()).clone();
+ me.lastTick = (me.lastTick || moment()).clone();
+ },
+ buildLabelDiffs: function() {
+ var me = this;
+ me.labelDiffs = [];
+ var scaleLabelDiffs = [];
+ // Parse common labels once
+ if (me.chart.data.labels && me.chart.data.labels.length > 0) {
+ helpers.each(me.chart.data.labels, function(label) {
+ var labelMoment = me.parseTime(label);
+
+ if (labelMoment.isValid()) {
+ if (me.options.time.round) {
+ labelMoment.startOf(me.options.time.round);
+ }
+ scaleLabelDiffs.push(labelMoment.diff(me.firstTick, me.tickUnit, true));
+ }
+ }, me);
+ }
+
+ helpers.each(me.chart.data.datasets, function(dataset) {
+ var diffsForDataset = [];
+
+ if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
+ helpers.each(dataset.data, function(value) {
+ var labelMoment = me.parseTime(me.getRightValue(value));
+
+ if (labelMoment.isValid()) {
+ if (me.options.time.round) {
+ labelMoment.startOf(me.options.time.round);
+ }
+ diffsForDataset.push(labelMoment.diff(me.firstTick, me.tickUnit, true));
+ }
+ }, me);
+ } else {
+ // We have no labels. Use common ones
+ diffsForDataset = scaleLabelDiffs;
+ }
+
+ me.labelDiffs.push(diffsForDataset);
+ }, me);
+ },
+ buildTicks: function() {
+ var me = this;
+
+ me.ctx.save();
+ var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
+ var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
+ var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
+ me.ctx.font = tickLabelFont;
+
+ me.ticks = [];
+ me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
+ me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)
+
+ // Set unit override if applicable
+ if (me.options.time.unit) {
+ me.tickUnit = me.options.time.unit || 'day';
+ me.displayFormat = me.options.time.displayFormats[me.tickUnit];
+ me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
+ me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1);
+ } else {
+ // Determine the smallest needed unit of the time
+ var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
+
+ // Crude approximation of what the label length might be
+ var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []);
+ var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width;
+ var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation));
+ var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation));
+ tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
+ var labelCapacity = innerWidth / (tickLabelWidth);
+
+ // Start as small as possible
+ me.tickUnit = me.options.time.minUnit;
+ me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
+ me.displayFormat = me.options.time.displayFormats[me.tickUnit];
+
+ var unitDefinitionIndex = 0;
+ var unitDefinition = time.units[unitDefinitionIndex];
+
+ // While we aren't ideal and we don't have units left
+ while (unitDefinitionIndex < time.units.length) {
+ // Can we scale this unit. If `false` we can scale infinitely
+ me.unitScale = 1;
+
+ if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
+ // Use one of the prefedined steps
+ for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
+ if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) {
+ me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]);
+ break;
+ }
+ }
+
+ break;
+ } else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
+ // We have a max step. Scale this unit
+ me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity));
+ break;
+ } else {
+ // Move to the next unit up
+ ++unitDefinitionIndex;
+ unitDefinition = time.units[unitDefinitionIndex];
+
+ me.tickUnit = unitDefinition.name;
+ var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true);
+ var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true);
+ me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer;
+ me.displayFormat = me.options.time.displayFormats[unitDefinition.name];
+ }
+ }
+ }
+
+ var roundedStart;
+
+ // Only round the first tick if we have no hard minimum
+ if (!me.options.time.min) {
+ me.firstTick = me.getMomentStartOf(me.firstTick);
+ roundedStart = me.firstTick;
+ } else {
+ roundedStart = me.getMomentStartOf(me.firstTick);
+ }
+
+ // Only round the last tick if we have no hard maximum
+ if (!me.options.time.max) {
+ var roundedEnd = me.getMomentStartOf(me.lastTick);
+ var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true);
+ if (delta < 0) {
+ // Do not use end of because we need me to be in the next time unit
+ me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit));
+ } else if (delta >= 0) {
+ me.lastTick = roundedEnd;
+ }
+
+ me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
+ }
+
+ // Tick displayFormat override
+ if (me.options.time.displayFormat) {
+ me.displayFormat = me.options.time.displayFormat;
+ }
+
+ // first tick. will have been rounded correctly if options.time.min is not specified
+ me.ticks.push(me.firstTick.clone());
+
+ // For every unit in between the first and last moment, create a moment and add it to the ticks tick
+ for (var i = 1; i <= me.scaleSizeInUnits; ++i) {
+ var newTick = roundedStart.clone().add(i, me.tickUnit);
+
+ // Are we greater than the max time
+ if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) {
+ break;
+ }
+
+ if (i % me.unitScale === 0) {
+ me.ticks.push(newTick);
+ }
+ }
+
+ // Always show the right tick
+ var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit);
+ if (diff !== 0 || me.scaleSizeInUnits === 0) {
+ // this is a weird case. If the option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
+ // but the last tick was not rounded.
+ if (me.options.time.max) {
+ me.ticks.push(me.lastTick.clone());
+ me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true);
+ } else {
+ me.ticks.push(me.lastTick.clone());
+ me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
+ }
+ }
+
+ me.ctx.restore();
+
+ // Invalidate label diffs cache
+ me.labelDiffs = undefined;
+ },
+ // Get tooltip label
+ getLabelForIndex: function(index, datasetIndex) {
+ var me = this;
+ var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : '';
+
+ if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') {
+ label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]);
+ }
+
+ // Format nicely
+ if (me.options.time.tooltipFormat) {
+ label = me.parseTime(label).format(me.options.time.tooltipFormat);
+ }
+
+ return label;
+ },
+ // Function to format an individual tick mark
+ tickFormatFunction: function(tick, index, ticks) {
+ var formattedTick = tick.format(this.displayFormat);
+ var tickOpts = this.options.ticks;
+ var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
+
+ if (callback) {
+ return callback(formattedTick, index, ticks);
+ }
+ return formattedTick;
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+ me.tickMoments = me.ticks;
+ me.ticks = me.ticks.map(me.tickFormatFunction, me);
+ },
+ getPixelForValue: function(value, index, datasetIndex) {
+ var me = this;
+ var offset = null;
+ if (index !== undefined && datasetIndex !== undefined) {
+ offset = me.getLabelDiff(datasetIndex, index);
+ }
+
+ if (offset === null) {
+ if (!value || !value.isValid) {
+ // not already a moment object
+ value = me.parseTime(me.getRightValue(value));
+ }
+ if (value && value.isValid && value.isValid()) {
+ offset = value.diff(me.firstTick, me.tickUnit, true);
+ }
+ }
+
+ if (offset !== null) {
+ var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset;
+
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var valueOffset = (innerWidth * decimal) + me.paddingLeft;
+
+ return me.left + Math.round(valueOffset);
+ }
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
+ var heightOffset = (innerHeight * decimal) + me.paddingTop;
+
+ return me.top + Math.round(heightOffset);
+ }
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.tickMoments[index], null, null);
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
+ var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension;
+ offset *= me.scaleSizeInUnits;
+ return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds');
+ },
+ parseTime: function(label) {
+ var me = this;
+ if (typeof me.options.time.parser === 'string') {
+ return moment(label, me.options.time.parser);
+ }
+ if (typeof me.options.time.parser === 'function') {
+ return me.options.time.parser(label);
+ }
+ // Date objects
+ if (typeof label.getMonth === 'function' || typeof label === 'number') {
+ return moment(label);
+ }
+ // Moment support
+ if (label.isValid && label.isValid()) {
+ return label;
+ }
+ // Custom parsing (return an instance of moment)
+ if (typeof me.options.time.format !== 'string' && me.options.time.format.call) {
+ console.warn('options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale');
+ return me.options.time.format(label);
+ }
+ // Moment format parsing
+ return moment(label, me.options.time.format);
+ }
+ });
+ Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
+
+};
+
+},{"1":1}]},{},[7])(7)
+});
\ No newline at end of file
diff --git a/web/resource/components/clockpicker/clockpicker.min.css b/web/resource/components/clockpicker/clockpicker.min.css
new file mode 100644
index 0000000..3d64c6b
--- /dev/null
+++ b/web/resource/components/clockpicker/clockpicker.min.css
@@ -0,0 +1 @@
+.clockpicker-moving{cursor:move}.clockpicker-align-left.popover>.arrow{left:25px}.clockpicker-align-top.popover>.arrow{top:17px}.clockpicker-align-right.popover>.arrow{left:auto;right:25px}.clockpicker-align-bottom.popover>.arrow{top:auto;bottom:6px}.clockpicker-popover .popover-title{background-color:#fff;color:#999;font-size:24px;font-weight:700;line-height:30px;text-align:center}.clockpicker-popover .popover-content{background-color:#f8f8f8;padding:12px}.popover-content:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.clockpicker-plate{background-color:#fff;border:1px solid #ccc;border-radius:50%;width:200px;height:200px;overflow:visible;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.clockpicker-canvas,.clockpicker-dial{width:200px;height:200px;position:absolute;left:-1px;top:-1px}.clockpicker-minutes{visibility:hidden}.clockpicker-tick{border-radius:50%;color:#666;line-height:26px;text-align:center;width:26px;height:26px;position:absolute;cursor:pointer}.clockpicker-tick.active,.clockpicker-tick:hover{background-color:rgba(0,149,221,.25)}.clockpicker-button{background-image:none;background-color:#fff;border-top-left-radius:0;border-top-right-radius:0;border-width:1px 0 0;margin:0;padding:10px 0}.clockpicker-button:hover{background-image:none;background-color:#ebebeb}.clockpicker-button:focus{outline:0!important}.clockpicker-dial{-webkit-transition:0 350ms opacity 350ms;-moz-transition:0 350ms opacity 350ms;-ms-transition:0 350ms opacity 350ms;-o-transition:0 350ms opacity 350ms;transition:transform 350ms opacity 350ms}.clockpicker-dial-out{opacity:0}.clockpicker-hours.clockpicker-dial-out{-webkit-transform:scale(1.2,1.2);-moz-transform:scale(1.2,1.2);-ms-transform:scale(1.2,1.2);-o-transform:scale(1.2,1.2);transform:scale(1.2,1.2)}.clockpicker-minutes.clockpicker-dial-out{-webkit-transform:scale(.8,.8);-moz-transform:scale(.8,.8);-ms-transform:scale(.8,.8);-o-transform:scale(.8,.8);transform:scale(.8,.8)}.clockpicker-canvas{-webkit-transition:opacity 175ms;-moz-transition:opacity 175ms;-ms-transition:opacity 175ms;-o-transition:opacity 175ms;transition:opacity 175ms}.clockpicker-canvas-out{opacity:.25}.clockpicker-canvas-bearing,.clockpicker-canvas-fg{stroke:none;fill:#0095dd}.clockpicker-canvas-bg{stroke:none;fill:#c0e5f7}.clockpicker-canvas-bg-trans{fill:rgba(0,149,221,.25)}.clockpicker-canvas line{stroke:#0095dd;stroke-width:1;stroke-linecap:round}.clockpicker-button.am-button{border:1px solid rgba(0,0,0,.2);border-radius:4px;margin:1px;padding:5px}.clockpicker-button.pm-button{border:1px solid rgba(0,0,0,.2);border-radius:4px;margin:1px 1px 1px 136px;padding:5px}.clockpicker .input-group-addon,.clockpicker-popover .popover-title span{cursor:pointer}
\ No newline at end of file
diff --git a/web/resource/components/clockpicker/clockpicker.min.js b/web/resource/components/clockpicker/clockpicker.min.js
new file mode 100644
index 0000000..c8006a3
--- /dev/null
+++ b/web/resource/components/clockpicker/clockpicker.min.js
@@ -0,0 +1,6 @@
+/*!
+ * ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei.
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ */
+!function(){function t(t){return document.createElementNS(p,t)}function i(t){return(10>t?"0":"")+t}function e(t){var i=++m+"";return t?t+i:i}function s(s,r){function p(t,i){var e=u.offset(),s=/^touch/.test(t.type),o=e.left+b,n=e.top+b,p=(s?t.originalEvent.touches[0]:t).pageX-o,h=(s?t.originalEvent.touches[0]:t).pageY-n,k=Math.sqrt(p*p+h*h),v=!1;if(!i||!(g-y>k||k>g+y)){t.preventDefault();var m=setTimeout(function(){c.addClass("clockpicker-moving")},200);l&&u.append(x.canvas),x.setHand(p,h,!i,!0),a.off(d).on(d,function(t){t.preventDefault();var i=/^touch/.test(t.type),e=(i?t.originalEvent.touches[0]:t).pageX-o,s=(i?t.originalEvent.touches[0]:t).pageY-n;(v||e!==p||s!==h)&&(v=!0,x.setHand(e,s,!1,!0))}),a.off(f).on(f,function(t){a.off(f),t.preventDefault();var e=/^touch/.test(t.type),s=(e?t.originalEvent.changedTouches[0]:t).pageX-o,l=(e?t.originalEvent.changedTouches[0]:t).pageY-n;(i||v)&&s===p&&l===h&&x.setHand(s,l),"hours"===x.currentView?x.toggleView("minutes",A/2):r.autoclose&&(x.minutesView.addClass("clockpicker-dial-out"),setTimeout(function(){x.done()},A/2)),u.prepend(j),clearTimeout(m),c.removeClass("clockpicker-moving"),a.off(d)})}}var h=n(V),u=h.find(".clockpicker-plate"),v=h.find(".clockpicker-hours"),m=h.find(".clockpicker-minutes"),T=h.find(".clockpicker-am-pm-block"),C="INPUT"===s.prop("tagName"),H=C?s:s.find("input"),P=s.find(".input-group-addon"),x=this;if(this.id=e("cp"),this.element=s,this.options=r,this.isAppended=!1,this.isShown=!1,this.currentView="hours",this.isInput=C,this.input=H,this.addon=P,this.popover=h,this.plate=u,this.hoursView=v,this.minutesView=m,this.amPmBlock=T,this.spanHours=h.find(".clockpicker-span-hours"),this.spanMinutes=h.find(".clockpicker-span-minutes"),this.spanAmPm=h.find(".clockpicker-span-am-pm"),this.amOrPm="PM",r.twelvehour){{var S=['','",'",""].join("");n(S)}n('').on("click",function(){x.amOrPm="AM",n(".clockpicker-span-am-pm").empty().append("AM")}).appendTo(this.amPmBlock),n('').on("click",function(){x.amOrPm="PM",n(".clockpicker-span-am-pm").empty().append("PM")}).appendTo(this.amPmBlock)}r.autoclose||n('").click(n.proxy(this.done,this)).appendTo(h),"top"!==r.placement&&"bottom"!==r.placement||"top"!==r.align&&"bottom"!==r.align||(r.align="left"),"left"!==r.placement&&"right"!==r.placement||"left"!==r.align&&"right"!==r.align||(r.align="top"),h.addClass(r.placement),h.addClass("clockpicker-align-"+r.align),this.spanHours.click(n.proxy(this.toggleView,this,"hours")),this.spanMinutes.click(n.proxy(this.toggleView,this,"minutes")),H.on("focus.clockpicker click.clockpicker",n.proxy(this.show,this)),P.on("click.clockpicker",n.proxy(this.toggle,this));var E,D,I,B,z=n('');if(r.twelvehour)for(E=1;13>E;E+=1)D=z.clone(),I=E/6*Math.PI,B=g,D.css("font-size","120%"),D.css({left:b+Math.sin(I)*B-y,top:b-Math.cos(I)*B-y}),D.html(0===E?"00":E),v.append(D),D.on(k,p);else for(E=0;24>E;E+=1){D=z.clone(),I=E/6*Math.PI;var O=E>0&&13>E;B=O?w:g,D.css({left:b+Math.sin(I)*B-y,top:b-Math.cos(I)*B-y}),O&&D.css("font-size","120%"),D.html(0===E?"00":E),v.append(D),D.on(k,p)}for(E=0;60>E;E+=5)D=z.clone(),I=E/30*Math.PI,D.css({left:b+Math.sin(I)*g-y,top:b-Math.cos(I)*g-y}),D.css("font-size","120%"),D.html(i(E)),m.append(D),D.on(k,p);if(u.on(k,function(t){0===n(t.target).closest(".clockpicker-tick").length&&p(t,!0)}),l){var j=h.find(".clockpicker-canvas"),L=t("svg");L.setAttribute("class","clockpicker-svg"),L.setAttribute("width",M),L.setAttribute("height",M);var U=t("g");U.setAttribute("transform","translate("+b+","+b+")");var W=t("circle");W.setAttribute("class","clockpicker-canvas-bearing"),W.setAttribute("cx",0),W.setAttribute("cy",0),W.setAttribute("r",2);var N=t("line");N.setAttribute("x1",0),N.setAttribute("y1",0);var X=t("circle");X.setAttribute("class","clockpicker-canvas-bg"),X.setAttribute("r",y);var Y=t("circle");Y.setAttribute("class","clockpicker-canvas-fg"),Y.setAttribute("r",3.5),U.appendChild(N),U.appendChild(X),U.appendChild(Y),U.appendChild(W),L.appendChild(U),j.append(L),this.hand=N,this.bg=X,this.fg=Y,this.bearing=W,this.g=U,this.canvas=j}o(this.options.init)}function o(t){t&&"function"==typeof t&&t()}var c,n=window.jQuery,r=n(window),a=n(document),p="http://www.w3.org/2000/svg",l="SVGAngle"in window&&function(){var t,i=document.createElement("div");return i.innerHTML="",t=(i.firstChild&&i.firstChild.namespaceURI)==p,i.innerHTML="",t}(),h=function(){var t=document.createElement("div").style;return"transition"in t||"WebkitTransition"in t||"MozTransition"in t||"msTransition"in t||"OTransition"in t}(),u="ontouchstart"in window,k="mousedown"+(u?" touchstart":""),d="mousemove.clockpicker"+(u?" touchmove.clockpicker":""),f="mouseup.clockpicker"+(u?" touchend.clockpicker":""),v=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,m=0,b=100,g=80,w=54,y=13,M=2*b,A=h?350:1,V=['','','',''," : ",'','',"",'','','','','',"",'',"","",""].join("");s.DEFAULTS={"default":"",fromnow:0,placement:"bottom",align:"left",donetext:"完成",autoclose:!1,twelvehour:!1,vibrate:!0},s.prototype.toggle=function(){this[this.isShown?"hide":"show"]()},s.prototype.locate=function(){var t=this.element,i=this.popover,e=t.offset(),s=t.outerWidth(),o=t.outerHeight(),c=this.options.placement,n=this.options.align,r={};switch(i.show(),c){case"bottom":r.top=e.top+o;break;case"right":r.left=e.left+s;break;case"top":r.top=e.top-i.outerHeight();break;case"left":r.left=e.left-i.outerWidth()}switch(n){case"left":r.left=e.left;break;case"right":r.left=e.left+s-i.outerWidth();break;case"top":r.top=e.top;break;case"bottom":r.top=e.top+o-i.outerHeight()}i.css(r)},s.prototype.show=function(){if(!this.isShown){o(this.options.beforeShow);var t=this;this.isAppended||(c=n(document.body).append(this.popover),r.on("resize.clockpicker"+this.id,function(){t.isShown&&t.locate()}),this.isAppended=!0);var e=((this.input.prop("value")||this.options["default"]||"")+"").split(":");if("now"===e[0]){var s=new Date(+new Date+this.options.fromnow);e=[s.getHours(),s.getMinutes()]}this.hours=+e[0]||0,this.minutes=+e[1]||0,this.spanHours.html(i(this.hours)),this.spanMinutes.html(i(this.minutes)),this.toggleView("hours"),this.locate(),this.isShown=!0,a.on("click.clockpicker."+this.id+" focusin.clockpicker."+this.id,function(i){var e=n(i.target);0===e.closest(t.popover).length&&0===e.closest(t.addon).length&&0===e.closest(t.input).length&&t.hide()}),a.on("keyup.clockpicker."+this.id,function(i){27===i.keyCode&&t.hide()}),o(this.options.afterShow)}},s.prototype.hide=function(){o(this.options.beforeHide),this.isShown=!1,a.off("click.clockpicker."+this.id+" focusin.clockpicker."+this.id),a.off("keyup.clockpicker."+this.id),this.popover.hide(),o(this.options.afterHide)},s.prototype.toggleView=function(t,i){var e=!1;"minutes"===t&&"visible"===n(this.hoursView).css("visibility")&&(o(this.options.beforeHourSelect),e=!0);var s="hours"===t,c=s?this.hoursView:this.minutesView,r=s?this.minutesView:this.hoursView;this.currentView=t,this.spanHours.toggleClass("text-primary",s),this.spanMinutes.toggleClass("text-primary",!s),r.addClass("clockpicker-dial-out"),c.css("visibility","visible").removeClass("clockpicker-dial-out"),this.resetClock(i),clearTimeout(this.toggleViewTimer),this.toggleViewTimer=setTimeout(function(){r.css("visibility","hidden")},A),e&&o(this.options.afterHourSelect)},s.prototype.resetClock=function(t){var i=this.currentView,e=this[i],s="hours"===i,o=Math.PI/(s?6:30),c=e*o,n=s&&e>0&&13>e?w:g,r=Math.sin(c)*n,a=-Math.cos(c)*n,p=this;l&&t?(p.canvas.addClass("clockpicker-canvas-out"),setTimeout(function(){p.canvas.removeClass("clockpicker-canvas-out"),p.setHand(r,a)},t)):this.setHand(r,a)},s.prototype.setHand=function(t,e,s,o){var c,r=Math.atan2(t,-e),a="hours"===this.currentView,p=Math.PI/(a||s?6:30),h=Math.sqrt(t*t+e*e),u=this.options,k=a&&(g+w)/2>h,d=k?w:g;if(u.twelvehour&&(d=g),0>r&&(r=2*Math.PI+r),c=Math.round(r/p),r=c*p,u.twelvehour?a?0===c&&(c=12):(s&&(c*=5),60===c&&(c=0)):a?(12===c&&(c=0),c=k?0===c?12:c:0===c?0:c+12):(s&&(c*=5),60===c&&(c=0)),this[this.currentView]!==c&&v&&this.options.vibrate&&(this.vibrateTimer||(navigator[v](10),this.vibrateTimer=setTimeout(n.proxy(function(){this.vibrateTimer=null},this),100))),this[this.currentView]=c,this[a?"spanHours":"spanMinutes"].html(i(c)),!l)return void this[a?"hoursView":"minutesView"].find(".clockpicker-tick").each(function(){var t=n(this);t.toggleClass("active",c===+t.html())});o||!a&&c%5?(this.g.insertBefore(this.hand,this.bearing),this.g.insertBefore(this.bg,this.fg),this.bg.setAttribute("class","clockpicker-canvas-bg clockpicker-canvas-bg-trans")):(this.g.insertBefore(this.hand,this.bg),this.g.insertBefore(this.fg,this.bg),this.bg.setAttribute("class","clockpicker-canvas-bg"));var f=Math.sin(r)*d,m=-Math.cos(r)*d;this.hand.setAttribute("x2",f),this.hand.setAttribute("y2",m),this.bg.setAttribute("cx",f),this.bg.setAttribute("cy",m),this.fg.setAttribute("cx",f),this.fg.setAttribute("cy",m)},s.prototype.done=function(){o(this.options.beforeDone),this.hide();var t=this.input.prop("value"),e=i(this.hours)+":"+i(this.minutes);this.options.twelvehour&&(e+=this.amOrPm),this.input.prop("value",e),e!==t&&(this.input.triggerHandler("change"),this.isInput||this.element.trigger("change")),this.options.autoclose&&this.input.trigger("blur"),o(this.options.afterDone)},s.prototype.remove=function(){this.element.removeData("clockpicker"),this.input.off("focus.clockpicker click.clockpicker"),this.addon.off("click.clockpicker"),this.isShown&&this.hide(),this.isAppended&&(r.off("resize.clockpicker"+this.id),this.popover.remove())},n.fn.clockpicker=function(t){var i=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=n(this),o=e.data("clockpicker");if(o)"function"==typeof o[t]&&o[t].apply(o,i);else{var c=n.extend({},s.DEFAULTS,e.data(),"object"==typeof t&&t);e.data("clockpicker",new s(e,c))}})}}();
\ No newline at end of file
diff --git a/web/resource/components/daterangepicker/daterangepicker.css b/web/resource/components/daterangepicker/daterangepicker.css
new file mode 100644
index 0000000..fcc22e9
--- /dev/null
+++ b/web/resource/components/daterangepicker/daterangepicker.css
@@ -0,0 +1,9 @@
+/*!
+ * Stylesheet for the Date Range Picker, for use with Bootstrap 3.x
+ *
+ * Copyright 2013 Dan Grossman ( http://www.dangrossman.info )
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Built for http://www.improvely.com
+ */.daterangepicker.dropdown-menu{max-width:none;z-index:3000}.daterangepicker.opensleft .calendar,.daterangepicker.opensleft .ranges{float:left;margin:4px}.daterangepicker.opensright .calendar,.daterangepicker.opensright .ranges{float:right;margin:4px}.daterangepicker .ranges .range_inputs>div,.daterangepicker_start_input{float:left}.daterangepicker .ranges{width:175px;text-align:left}.daterangepicker .ranges .range_inputs>div:nth-child(2){padding-left:11px}.daterangepicker .calendar{display:none;max-width:270px}.daterangepicker.show-calendar .calendar{display:block}.daterangepicker .calendar.single .calendar-date{border:none}.daterangepicker .calendar td,.daterangepicker .calendar th{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;white-space:nowrap;text-align:center;min-width:32px}.daterangepicker .daterangepicker_end_input label,.daterangepicker .daterangepicker_start_input label{color:#333;display:block;font-size:11px;font-weight:400;height:20px;line-height:20px;margin-bottom:2px;text-shadow:#fff 1px 1px 0;text-transform:uppercase;width:74px}.daterangepicker .ranges input{font-size:11px}.daterangepicker .ranges .input-mini{background-color:#eee;border:1px solid #ccc;border-radius:4px;color:#555;display:block;font-size:11px;height:30px;line-height:30px;vertical-align:middle;margin:0 0 10px;padding:0 6px;width:82px}.daterangepicker.opensleft:after,.daterangepicker.opensleft:before,.daterangepicker.opensright:after,.daterangepicker.opensright:before{position:absolute;display:inline-block;content:''}.daterangepicker .ranges ul{list-style:none;margin:0;padding:0}.daterangepicker .ranges li{font-size:13px;background:#f5f5f5;border:1px solid #f5f5f5;color:#08c;padding:3px 12px;margin-bottom:8px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;cursor:pointer}.daterangepicker .ranges li.active,.daterangepicker .ranges li:hover{background:#08c;border:1px solid #08c;color:#fff}.daterangepicker .calendar-date{border:1px solid #ddd;padding:4px;border-radius:4px;background:#fff}.daterangepicker .calendar-time{text-align:center;margin:8px auto 0;line-height:30px}.daterangepicker{position:absolute;background:#fff;top:100px;left:20px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.daterangepicker.opensleft:before{top:-7px;right:9px;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2)}.daterangepicker.opensleft:after{top:-6px;right:10px;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent}.daterangepicker.opensright:before{top:-7px;left:9px;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,.2)}.daterangepicker.opensright:after{top:-6px;left:10px;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent}.daterangepicker table{width:100%;margin:0}.daterangepicker td,.daterangepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;white-space:nowrap}.daterangepicker td.disabled,.daterangepicker td.off{color:#999}.daterangepicker td.available:hover,.daterangepicker th.available:hover{background:#eee}.daterangepicker td.in-range{background:#ebf4f8;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.daterangepicker td.active,.daterangepicker td.active:hover{background-color:#357ebd;border-color:#3071a9;color:#fff}.daterangepicker td.week,.daterangepicker th.week{font-size:80%;color:#ccc}.daterangepicker select.monthselect,.daterangepicker select.yearselect{font-size:12px;padding:1px;height:auto;margin:0;cursor:default}.daterangepicker select.monthselect{margin-right:2%;width:56%}.daterangepicker select.yearselect{width:40%}.daterangepicker select.ampmselect,.daterangepicker select.hourselect,.daterangepicker select.minuteselect{width:50px;margin-bottom:0}.daterangepicker_end_input{float:left;padding-left:11px}.daterangepicker th.month{width:auto}
\ No newline at end of file
diff --git a/web/resource/components/daterangepicker/daterangepicker.js b/web/resource/components/daterangepicker/daterangepicker.js
new file mode 100644
index 0000000..17bae89
--- /dev/null
+++ b/web/resource/components/daterangepicker/daterangepicker.js
@@ -0,0 +1,1116 @@
+/**
+* @version: 1.3.8
+* @author: Dan Grossman http://www.dangrossman.info/
+* @date: 2014-07-10
+* @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved.
+* @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0
+* @website: http://www.improvely.com/
+*/
+!function ($, moment) {
+
+ var DateRangePicker = function (element, options, cb) {
+
+ // by default, the daterangepicker element is placed at the bottom of HTML body
+ this.parentEl = 'body';
+
+ //element that triggered the date range picker
+ this.element = $(element);
+
+ //tracks visible state
+ this.isShowing = false;
+
+ //create the picker HTML object
+ var DRPTemplate = '';
+
+ //custom options
+ if (typeof options !== 'object' || options === null)
+ options = {};
+
+ this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
+ this.container = $(DRPTemplate).appendTo(this.parentEl);
+
+ this.setOptions(options, cb);
+
+ //apply CSS classes and labels to buttons
+ var c = this.container;
+ $.each(this.buttonClasses, function (idx, val) {
+ c.find('button').addClass(val);
+ });
+ this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel);
+ this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel);
+ if (this.applyClass.length)
+ this.container.find('.applyBtn').addClass(this.applyClass);
+ if (this.cancelClass.length)
+ this.container.find('.cancelBtn').addClass(this.cancelClass);
+ this.container.find('.applyBtn').html(this.locale.applyLabel);
+ this.container.find('.cancelBtn').html(this.locale.cancelLabel);
+
+ //event listeners
+
+ this.container.find('.calendar')
+ .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
+ .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
+ .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
+ .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.enterDate, this))
+ .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this))
+ .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this))
+ .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this))
+ .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this));
+
+ this.container.find('.ranges')
+ .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
+ .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
+ .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
+ .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
+ .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this))
+ .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this));
+
+ if (this.element.is('input')) {
+ this.element.on({
+ 'click.daterangepicker': $.proxy(this.show, this),
+ 'focus.daterangepicker': $.proxy(this.show, this),
+ 'keyup.daterangepicker': $.proxy(this.updateFromControl, this)
+ });
+ } else {
+ this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
+ }
+
+ };
+
+ DateRangePicker.prototype = {
+
+ constructor: DateRangePicker,
+
+ setOptions: function(options, callback) {
+
+ this.startDate = moment().startOf('minute');
+ this.endDate = moment().endOf('day');
+ this.minDate = false;
+ this.maxDate = false;
+ this.dateLimit = false;
+
+ this.showDropdowns = false;
+ this.showWeekNumbers = false;
+ this.timePicker = false;
+ this.timePickerIncrement = 30;
+ this.timePicker12Hour = true;
+ this.singleDatePicker = false;
+ this.ranges = {};
+
+ this.opens = 'right';
+ if (this.element.hasClass('pull-right'))
+ this.opens = 'left';
+
+ this.buttonClasses = ['btn', 'btn-small btn-sm'];
+ this.applyClass = 'btn-success';
+ this.cancelClass = 'btn-default';
+
+ this.format = 'MM/DD/YYYY';
+ this.separator = ' - ';
+
+ this.locale = {
+ applyLabel: 'Apply',
+ cancelLabel: 'Cancel',
+ fromLabel: 'From',
+ toLabel: 'To',
+ weekLabel: 'W',
+ customRangeLabel: 'Custom Range',
+ daysOfWeek: moment()._lang._weekdaysMin.slice(),
+ monthNames: moment()._lang._monthsShort.slice(),
+ firstDay: 0
+ };
+
+ this.cb = function () { };
+
+ if (typeof options.format === 'string')
+ this.format = options.format;
+
+ if (typeof options.separator === 'string')
+ this.separator = options.separator;
+
+ if (typeof options.startDate === 'string')
+ this.startDate = moment(options.startDate, this.format);
+
+ if (typeof options.endDate === 'string')
+ this.endDate = moment(options.endDate, this.format);
+
+ if (typeof options.minDate === 'string')
+ this.minDate = moment(options.minDate, this.format);
+
+ if (typeof options.maxDate === 'string')
+ this.maxDate = moment(options.maxDate, this.format);
+
+ if (typeof options.startDate === 'object')
+ this.startDate = moment(options.startDate);
+
+ if (typeof options.endDate === 'object')
+ this.endDate = moment(options.endDate);
+
+ if (typeof options.minDate === 'object')
+ this.minDate = moment(options.minDate);
+
+ if (typeof options.maxDate === 'object')
+ this.maxDate = moment(options.maxDate);
+
+ if (typeof options.applyClass === 'string')
+ this.applyClass = options.applyClass;
+
+ if (typeof options.cancelClass === 'string')
+ this.cancelClass = options.cancelClass;
+
+ if (typeof options.dateLimit === 'object')
+ this.dateLimit = options.dateLimit;
+
+ // update day names order to firstDay
+ if (typeof options.locale === 'object') {
+
+ if (typeof options.locale.daysOfWeek === 'object') {
+ // Create a copy of daysOfWeek to avoid modification of original
+ // options object for reusability in multiple daterangepicker instances
+ this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
+ }
+
+ if (typeof options.locale.monthNames === 'object') {
+ this.locale.monthNames = options.locale.monthNames.slice();
+ }
+
+ if (typeof options.locale.firstDay === 'number') {
+ this.locale.firstDay = options.locale.firstDay;
+ var iterator = options.locale.firstDay;
+ while (iterator > 0) {
+ this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
+ iterator--;
+ }
+ }
+
+ if (typeof options.locale.applyLabel === 'string') {
+ this.locale.applyLabel = options.locale.applyLabel;
+ }
+
+ if (typeof options.locale.cancelLabel === 'string') {
+ this.locale.cancelLabel = options.locale.cancelLabel;
+ }
+
+ if (typeof options.locale.fromLabel === 'string') {
+ this.locale.fromLabel = options.locale.fromLabel;
+ }
+
+ if (typeof options.locale.toLabel === 'string') {
+ this.locale.toLabel = options.locale.toLabel;
+ }
+
+ if (typeof options.locale.weekLabel === 'string') {
+ this.locale.weekLabel = options.locale.weekLabel;
+ }
+
+ if (typeof options.locale.customRangeLabel === 'string') {
+ this.locale.customRangeLabel = options.locale.customRangeLabel;
+ }
+ }
+
+ if (typeof options.opens === 'string')
+ this.opens = options.opens;
+
+ if (typeof options.showWeekNumbers === 'boolean') {
+ this.showWeekNumbers = options.showWeekNumbers;
+ }
+
+ if (typeof options.buttonClasses === 'string') {
+ this.buttonClasses = [options.buttonClasses];
+ }
+
+ if (typeof options.buttonClasses === 'object') {
+ this.buttonClasses = options.buttonClasses;
+ }
+
+ if (typeof options.showDropdowns === 'boolean') {
+ this.showDropdowns = options.showDropdowns;
+ }
+
+ if (typeof options.singleDatePicker === 'boolean') {
+ this.singleDatePicker = options.singleDatePicker;
+ }
+
+ if (typeof options.timePicker === 'boolean') {
+ this.timePicker = options.timePicker;
+ }
+
+ if (typeof options.timePickerIncrement === 'number') {
+ this.timePickerIncrement = options.timePickerIncrement;
+ }
+
+ if (typeof options.timePicker12Hour === 'boolean') {
+ this.timePicker12Hour = options.timePicker12Hour;
+ }
+
+ var start, end, range;
+
+ //if no start/end dates set, check if an input element contains initial values
+ if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
+ if ($(this.element).is('input[type=text]')) {
+ var val = $(this.element).val();
+ var split = val.split(this.separator);
+ start = end = null;
+ if (split.length == 2) {
+ start = moment(split[0], this.format);
+ end = moment(split[1], this.format);
+ } else if (this.singleDatePicker) {
+ start = moment(val, this.format);
+ end = moment(val, this.format);
+ }
+ if (start !== null && end !== null) {
+ this.startDate = start;
+ this.endDate = end;
+ }
+ }
+ }
+
+ if (typeof options.ranges === 'object') {
+ for (range in options.ranges) {
+
+ start = moment(options.ranges[range][0]);
+ end = moment(options.ranges[range][1]);
+
+ // If we have a min/max date set, bound this range
+ // to it, but only if it would otherwise fall
+ // outside of the min/max.
+ if (this.minDate && start.isBefore(this.minDate))
+ start = moment(this.minDate);
+
+ if (this.maxDate && end.isAfter(this.maxDate))
+ end = moment(this.maxDate);
+
+ // If the end of the range is before the minimum (if min is set) OR
+ // the start of the range is after the max (also if set) don't display this
+ // range option.
+ if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) {
+ continue;
+ }
+
+ this.ranges[range] = [start, end];
+ }
+
+ var list = '';
+ for (range in this.ranges) {
+ list += '- ' + range + '
';
+ }
+ list += '- ' + this.locale.customRangeLabel + '
';
+ list += '
';
+ this.container.find('.ranges ul').remove();
+ this.container.find('.ranges').prepend(list);
+ }
+
+ if (typeof callback === 'function') {
+ this.cb = callback;
+ }
+
+ if (!this.timePicker) {
+ this.startDate = this.startDate.startOf('day');
+ this.endDate = this.endDate.endOf('day');
+ }
+
+ if (this.singleDatePicker) {
+ this.opens = 'right';
+ this.container.find('.calendar.right').show();
+ this.container.find('.calendar.left').hide();
+ this.container.find('.ranges').hide();
+ if (!this.container.find('.calendar.right').hasClass('single'))
+ this.container.find('.calendar.right').addClass('single');
+ } else {
+ this.container.find('.calendar.right').removeClass('single');
+ this.container.find('.ranges').show();
+ }
+
+ this.oldStartDate = this.startDate.clone();
+ this.oldEndDate = this.endDate.clone();
+ this.oldChosenLabel = this.chosenLabel;
+
+ this.leftCalendar = {
+ month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]),
+ calendar: []
+ };
+
+ this.rightCalendar = {
+ month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]),
+ calendar: []
+ };
+
+ if (this.opens == 'right') {
+ //swap calendar positions
+ var left = this.container.find('.calendar.left');
+ var right = this.container.find('.calendar.right');
+ left.removeClass('left').addClass('right');
+ right.removeClass('right').addClass('left');
+ }
+
+ if (typeof options.ranges === 'undefined' && !this.singleDatePicker) {
+ this.container.addClass('show-calendar');
+ }
+
+ this.container.addClass('opens' + this.opens);
+
+ this.updateView();
+ this.updateCalendars();
+
+ },
+
+ setStartDate: function(startDate) {
+ if (typeof startDate === 'string')
+ this.startDate = moment(startDate, this.format);
+
+ if (typeof startDate === 'object')
+ this.startDate = moment(startDate);
+
+ if (!this.timePicker)
+ this.startDate = this.startDate.startOf('day');
+
+ this.oldStartDate = this.startDate.clone();
+
+ this.updateView();
+ this.updateCalendars();
+ this.updateInputText();
+ },
+
+ setEndDate: function(endDate) {
+ if (typeof endDate === 'string')
+ this.endDate = moment(endDate, this.format);
+
+ if (typeof endDate === 'object')
+ this.endDate = moment(endDate);
+
+ if (!this.timePicker)
+ this.endDate = this.endDate.endOf('day');
+
+ this.oldEndDate = this.endDate.clone();
+
+ this.updateView();
+ this.updateCalendars();
+ this.updateInputText();
+ },
+
+ updateView: function () {
+
+ this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
+ this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
+ this.updateFormInputs();
+ },
+
+ updateFormInputs: function () {
+ this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
+ this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));
+ if(this.startDate.format(this.format) == '0000-01-01' && '0000-01-01' == this.startDate.format(this.format)) {
+ this.container.find('.daterangepicker_start_input').hide()
+ this.container.find('.daterangepicker_end_input').hide()
+ } else {
+ this.container.find('.daterangepicker_start_input').show()
+ this.container.find('.daterangepicker_end_input').show()
+ }
+ if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) {
+ this.container.find('button.applyBtn').removeAttr('disabled');
+ } else {
+ this.container.find('button.applyBtn').attr('disabled', 'disabled');
+ }
+ },
+
+ updateFromControl: function () {
+ if (!this.element.is('input')) return;
+ if (!this.element.val().length) return;
+
+ var dateString = this.element.val().split(this.separator),
+ start = null,
+ end = null;
+
+ if (dateString.length === 2) {
+ start = moment(dateString[0], this.format);
+ end = moment(dateString[1], this.format);
+ }
+
+ if (this.singleDatePicker || start === null || end === null) {
+ start = moment(this.element.val(), this.format);
+ end = start;
+ }
+
+ if (end.isBefore(start)) return;
+
+ this.oldStartDate = this.startDate.clone();
+ this.oldEndDate = this.endDate.clone();
+
+ this.startDate = start;
+ this.endDate = end;
+
+ if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
+ this.notify();
+
+ this.updateCalendars();
+ },
+
+ notify: function () {
+ this.updateView();
+ this.cb(this.startDate, this.endDate, this.chosenLabel);
+ },
+
+ move: function () {
+ var parentOffset = { top: 0, left: 0 };
+ if (!this.parentEl.is('body')) {
+ parentOffset = {
+ top: this.parentEl.offset().top - this.parentEl.scrollTop(),
+ left: this.parentEl.offset().left - this.parentEl.scrollLeft()
+ };
+ }
+
+ if (this.opens == 'left') {
+ this.container.css({
+ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
+ right: $(window).width() - this.element.offset().left - this.element.outerWidth() - parentOffset.left,
+ left: 'auto'
+ });
+ if (this.container.offset().left < 0) {
+ this.container.css({
+ right: 'auto',
+ left: 9
+ });
+ }
+ } else {
+ this.container.css({
+ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
+ left: this.element.offset().left - parentOffset.left,
+ right: 'auto'
+ });
+ if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
+ this.container.css({
+ left: 'auto',
+ right: 0
+ });
+ }
+ }
+ },
+
+ toggle: function (e) {
+ if (this.element.hasClass('active')) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ },
+
+ show: function (e) {
+ if (this.isShowing) return;
+
+ this.element.addClass('active');
+ this.container.show();
+ this.move();
+
+ // Create a click proxy that is private to this instance of datepicker, for unbinding
+ this._outsideClickProxy = $.proxy(function (e) {this.outsideClick(e);}, this);
+ // Bind global datepicker mousedown for hiding and
+ $(document)
+ .on('mousedown.daterangepicker', this._outsideClickProxy)
+ // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
+ .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
+ // and also close when focus changes to outside the picker (eg. tabbing between controls)
+ .on('focusin.daterangepicker', this._outsideClickProxy);
+
+ this.isShowing = true;
+ this.element.trigger('show.daterangepicker', this);
+ },
+
+ outsideClick: function (e) {
+ var target = $(e.target);
+ // if the page is clicked anywhere except within the daterangerpicker/button
+ // itself then call this.hide()
+ if (
+ target.closest(this.element).length ||
+ target.closest(this.container).length ||
+ target.closest('.calendar-date').length
+ ) return;
+ this.hide();
+ },
+
+ hide: function (e) {
+ if (!this.isShowing) return;
+
+ $(document)
+ .off('mousedown.daterangepicker')
+ .off('click.daterangepicker', '[data-toggle=dropdown]')
+ .off('focusin.daterangepicker');
+
+ this.element.removeClass('active');
+ this.container.hide();
+
+ if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
+ this.notify();
+
+ this.oldStartDate = this.startDate.clone();
+ this.oldEndDate = this.endDate.clone();
+
+ this.isShowing = false;
+ this.element.trigger('hide.daterangepicker', this);
+ },
+
+ enterRange: function (e) {
+ // mouse pointer has entered a range label
+ var label = e.target.innerHTML;
+ if (label == this.locale.customRangeLabel) {
+ date = new Date();
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ if (month < 10) {
+ month = '0' + month;
+ }
+ day = date.getDate();
+ week_date = this.addDate(month+'/'+day+'/'+year, -7);
+ week_year = week_date.getFullYear();
+ week_month = week_date.getMonth() + 1;
+ if (week_month < 10) {
+ week_month = '0' + week_month;
+ }
+ week_day = week_date.getDate();
+ this.container.find('input[name=daterangepicker_start]').val(week_year + '-' + week_month + '-' + week_day);
+ this.container.find('input[name=daterangepicker_end]').val(year + '-' + month + '-' + day);
+ } else {
+ var dates = this.ranges[label];
+ this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format));
+ this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format));
+ }
+ },
+
+ showCalendars: function() {
+ this.container.addClass('show-calendar');
+ this.move();
+ this.element.trigger('showCalendar.daterangepicker', this);
+ },
+
+ hideCalendars: function() {
+ this.container.removeClass('show-calendar');
+ this.element.trigger('hideCalendar.daterangepicker', this);
+ },
+
+ updateInputText: function() {
+ if (this.element.is('input') && !this.singleDatePicker) {
+ this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
+ } else if (this.element.is('input')) {
+ this.element.val(this.startDate.format(this.format));
+ } else {
+
+ }
+ },
+ addDate: function(date, count) {
+ var a = new Date(date);
+ a = a.valueOf();
+ a = a + count * 24 * 60 * 60 * 1000;
+ a = new Date(a);
+ return a;
+ },
+ clickRange: function (e) {
+ var label = e.target.innerHTML;
+ this.chosenLabel = label;
+ if (label == this.locale.customRangeLabel) {
+ this.showCalendars();
+
+ date = new Date();
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ if (month < 10) {
+ month = '0' + month;
+ }
+ day = date.getDate();
+ week_date = this.addDate(month+'/'+day+'/'+year, -7);
+ week_year = week_date.getFullYear();
+ week_month = week_date.getMonth() + 1;
+ if (week_month < 10) {
+ week_month = '0' + week_month;
+ }
+ week_day = week_date.getDate();
+ this.leftCalendar.month.month(week_month).year(week_year).hour(this.startDate.hour()).minute(this.startDate.minute());
+ this.rightCalendar.month.month(month).year(year).hour(this.endDate.hour()).minute(this.endDate.minute());
+ this.updateCalendars();
+ } else {
+ var dates = this.ranges[label];
+
+ this.startDate = dates[0];
+ this.endDate = dates[1];
+ if (!this.timePicker) {
+ this.startDate.startOf('day');
+ this.endDate.endOf('day');
+ }
+
+ this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
+ this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
+ this.updateCalendars();
+
+ this.updateInputText();
+
+ this.hideCalendars();
+ this.hide();
+ this.element.trigger('apply.daterangepicker', this);
+ }
+ },
+
+ clickPrev: function (e) {
+ var cal = $(e.target).parents('.calendar');
+ if (cal.hasClass('left')) {
+ this.leftCalendar.month.subtract('month', 1);
+ } else {
+ this.rightCalendar.month.subtract('month', 1);
+ }
+ this.updateCalendars();
+ },
+
+ clickNext: function (e) {
+ var cal = $(e.target).parents('.calendar');
+ if (cal.hasClass('left')) {
+ this.leftCalendar.month.add('month', 1);
+ } else {
+ this.rightCalendar.month.add('month', 1);
+ }
+ this.updateCalendars();
+ },
+
+ enterDate: function (e) {
+
+ var title = $(e.target).attr('data-title');
+ var row = title.substr(1, 1);
+ var col = title.substr(3, 1);
+ var cal = $(e.target).parents('.calendar');
+
+ if (cal.hasClass('left')) {
+ this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format));
+ } else {
+ this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format));
+ }
+
+ },
+
+ clickDate: function (e) {
+ var title = $(e.target).attr('data-title');
+ var row = title.substr(1, 1);
+ var col = title.substr(3, 1);
+ var cal = $(e.target).parents('.calendar');
+
+ var startDate, endDate;
+ if (cal.hasClass('left')) {
+ startDate = this.leftCalendar.calendar[row][col];
+ endDate = this.endDate;
+ if (typeof this.dateLimit === 'object') {
+ var maxDate = moment(startDate).add(this.dateLimit).startOf('day');
+ if (endDate.isAfter(maxDate)) {
+ endDate = maxDate;
+ }
+ }
+ } else {
+ startDate = this.startDate;
+ endDate = this.rightCalendar.calendar[row][col];
+ if (typeof this.dateLimit === 'object') {
+ var minDate = moment(endDate).subtract(this.dateLimit).startOf('day');
+ if (startDate.isBefore(minDate)) {
+ startDate = minDate;
+ }
+ }
+ }
+
+ if (this.singleDatePicker && cal.hasClass('left')) {
+ endDate = startDate.clone();
+ } else if (this.singleDatePicker && cal.hasClass('right')) {
+ startDate = endDate.clone();
+ }
+
+ cal.find('td').removeClass('active');
+
+ if (startDate.isSame(endDate) || startDate.isBefore(endDate)) {
+ $(e.target).addClass('active');
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.chosenLabel = this.locale.customRangeLabel;
+ } else if (startDate.isAfter(endDate)) {
+ $(e.target).addClass('active');
+ var difference = this.endDate.diff(this.startDate);
+ this.startDate = startDate;
+ this.endDate = moment(startDate).add('ms', difference);
+ this.chosenLabel = this.locale.customRangeLabel;
+ }
+
+ this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year());
+ this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year());
+ this.updateCalendars();
+
+ if (!this.timePicker)
+ endDate.endOf('day');
+
+ if (this.singleDatePicker)
+ this.clickApply();
+ },
+
+ clickApply: function (e) {
+ this.updateInputText();
+ this.hide();
+ this.element.trigger('apply.daterangepicker', this);
+ },
+
+ clickCancel: function (e) {
+ this.startDate = this.oldStartDate;
+ this.endDate = this.oldEndDate;
+ this.chosenLabel = this.oldChosenLabel;
+ this.updateView();
+ this.updateCalendars();
+ this.hide();
+ this.element.trigger('cancel.daterangepicker', this);
+ },
+
+ updateMonthYear: function (e) {
+ var isLeft = $(e.target).closest('.calendar').hasClass('left'),
+ leftOrRight = isLeft ? 'left' : 'right',
+ cal = this.container.find('.calendar.'+leftOrRight);
+
+ // Month must be Number for new moment versions
+ var month = parseInt(cal.find('.monthselect').val(), 10);
+ var year = cal.find('.yearselect').val();
+
+ this[leftOrRight+'Calendar'].month.month(month).year(year);
+ this.updateCalendars();
+ },
+
+ updateTime: function(e) {
+
+ var cal = $(e.target).closest('.calendar'),
+ isLeft = cal.hasClass('left');
+
+ var hour = parseInt(cal.find('.hourselect').val(), 10);
+ var minute = parseInt(cal.find('.minuteselect').val(), 10);
+
+ if (this.timePicker12Hour) {
+ var ampm = cal.find('.ampmselect').val();
+ if (ampm === 'PM' && hour < 12)
+ hour += 12;
+ if (ampm === 'AM' && hour === 12)
+ hour = 0;
+ }
+
+ if (isLeft) {
+ var start = this.startDate.clone();
+ start.hour(hour);
+ start.minute(minute);
+ this.startDate = start;
+ this.leftCalendar.month.hour(hour).minute(minute);
+ } else {
+ var end = this.endDate.clone();
+ end.hour(hour);
+ end.minute(minute);
+ this.endDate = end;
+ this.rightCalendar.month.hour(hour).minute(minute);
+ }
+
+ this.updateCalendars();
+ },
+
+ updateCalendars: function () {
+ this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left');
+ this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right');
+ this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate));
+ this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate));
+
+ this.container.find('.ranges li').removeClass('active');
+ var customRange = true;
+ var i = 0;
+ for (var range in this.ranges) {
+ if (this.timePicker) {
+ if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
+ customRange = false;
+ this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
+ .addClass('active').html();
+ }
+ } else {
+ //ignore times when comparing dates if time picker is not enabled
+ if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
+ customRange = false;
+ this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
+ .addClass('active').html();
+ }
+ }
+ i++;
+ }
+ if (customRange) {
+ this.chosenLabel = this.container.find('.ranges li:last')
+ .addClass('active').html();
+ }
+ },
+
+ buildCalendar: function (month, year, hour, minute, side) {
+ var firstDay = moment([year, month, 1]);
+ var lastMonth = moment(firstDay).subtract('month', 1).month();
+ var lastYear = moment(firstDay).subtract('month', 1).year();
+
+ var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
+
+ var dayOfWeek = firstDay.day();
+
+ var i;
+
+ //initialize a 6 rows x 7 columns array for the calendar
+ var calendar = [];
+ for (i = 0; i < 6; i++) {
+ calendar[i] = [];
+ }
+
+ //populate the calendar with date objects
+ var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
+ if (startDay > daysInLastMonth)
+ startDay -= 7;
+
+ if (dayOfWeek == this.locale.firstDay)
+ startDay = daysInLastMonth - 6;
+
+ var curDate = moment([lastYear, lastMonth, startDay, 12, minute]);
+ var col, row;
+ for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) {
+ if (i > 0 && col % 7 === 0) {
+ col = 0;
+ row++;
+ }
+ calendar[row][col] = curDate.clone().hour(hour);
+ curDate.hour(12);
+ }
+
+ return calendar;
+ },
+
+ renderDropdowns: function (selected, minDate, maxDate) {
+ var currentMonth = selected.month();
+ var monthHtml = '';
+
+ var currentYear = selected.year();
+ var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
+ var minYear = (minDate && minDate.year()) || (currentYear - 50);
+ var yearHtml = '';
+
+ return monthHtml + yearHtml;
+ },
+
+ renderCalendar: function (calendar, selected, minDate, maxDate) {
+
+ var html = '';
+ html += '';
+ html += '';
+ html += '';
+
+ // add empty cell for week number
+ if (this.showWeekNumbers)
+ html += ' ';
+
+ if (!minDate || minDate.isBefore(calendar[1][1])) {
+ html += ' ';
+ } else {
+ html += ' ';
+ }
+
+ var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(' YYYY');
+
+ if (this.showDropdowns) {
+ dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate);
+ }
+
+ html += '' + dateHtml + ' ';
+ if (!maxDate || maxDate.isAfter(calendar[1][1])) {
+ html += ' ';
+ } else {
+ html += ' ';
+ }
+
+ html += ' ';
+ html += '';
+
+ // add week number label
+ if (this.showWeekNumbers)
+ html += '' + this.locale.weekLabel + ' ';
+
+ $.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
+ html += '' + dayOfWeek + ' ';
+ });
+
+ html += ' ';
+ html += '';
+ html += '';
+
+ for (var row = 0; row < 6; row++) {
+ html += '';
+
+ // add week number
+ if (this.showWeekNumbers)
+ html += '' + calendar[row][0].week() + ' ';
+
+ for (var col = 0; col < 7; col++) {
+ var cname = 'available ';
+ cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off';
+
+ if ((minDate && calendar[row][col].isBefore(minDate, 'day')) || (maxDate && calendar[row][col].isAfter(maxDate, 'day'))) {
+ cname = ' off disabled ';
+ } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) {
+ cname += ' active ';
+ if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) {
+ cname += ' start-date ';
+ }
+ if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) {
+ cname += ' end-date ';
+ }
+ } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) {
+ cname += ' in-range ';
+ if (calendar[row][col].isSame(this.startDate)) {cname += ' start-date ';}
+ if (calendar[row][col].isSame(this.endDate)) {cname += ' end-date ';}
+ }
+
+ var title = 'r' + row + 'c' + col;
+ html += '' + calendar[row][col].date() + ' ';
+ }
+ html += ' ';
+ }
+
+ html += '';
+ html += '
';
+ html += '';
+
+ var i;
+ if (this.timePicker) {
+
+ html += '';
+ html += ' : ';
+
+ html += ' ';
+
+ if (this.timePicker12Hour) {
+ html += '';
+ }
+
+ html += '';
+
+ }
+
+ return html;
+
+ },
+
+ remove: function() {
+
+ this.container.remove();
+ this.element.off('.daterangepicker');
+ this.element.removeData('daterangepicker');
+
+ }
+
+ };
+
+ $.fn.daterangepicker = function (options, cb) {
+ this.each(function () {
+ var el = $(this);
+ if (el.data('daterangepicker'))
+ el.data('daterangepicker').remove();
+
+ if (typeof(options) == 'object') {
+ if (typeof(options.ranges) != 'object') {
+ options.ranges = {};
+ var now = new Date();
+ var year = now.getFullYear();
+ var month = now.getMonth();
+ var day = now.getDate();
+ // options.ranges['今天'] = [new Date(year, month, day, 0, 0, 0), moment()];
+ if(options.clear) {
+ options.ranges['不限时间'] = [moment('不限时间', 'Y'), moment('不限时间', 'Y')];
+ }
+ options.ranges['一周内'] = [moment().subtract('days', 6), moment()];
+ options.ranges['二周内'] = [moment().subtract('days', 13), moment()];
+ options.ranges['一月内'] = [moment().subtract('days', 29), moment()];
+ }
+ if (typeof(options.locale) != 'object') {
+ options.locale = {
+ applyLabel: '确定',
+ cancelLabel: '取消',
+ fromLabel: '从',
+ toLabel: '至',
+ weekLabel: '周',
+ customRangeLabel: '日期范围',
+ daysOfWeek: moment()._lang._weekdaysMin.slice(),
+ monthNames: moment()._lang._monthsShort.slice(),
+ firstDay: 0
+ };
+ }
+ }
+ el.data('daterangepicker', new DateRangePicker(el, options, cb));
+ });
+ return this;
+ };
+
+}(window.jQuery, window.moment);
diff --git a/web/resource/components/kindeditor/plugins/clearhtml/clearhtml.js b/web/resource/components/kindeditor/plugins/clearhtml/clearhtml.js
new file mode 100644
index 0000000..1bf0e5d
--- /dev/null
+++ b/web/resource/components/kindeditor/plugins/clearhtml/clearhtml.js
@@ -0,0 +1,29 @@
+/*******************************************************************************
+* KindEditor - WYSIWYG HTML Editor for Internet
+* Copyright (C) 2006-2011 kindsoft.net
+*
+* @author Roddy
+* @site http://www.kindsoft.net/
+* @licence http://www.kindsoft.net/license.php
+*******************************************************************************/
+
+KindEditor.plugin('clearhtml', function(K) {
+ var self = this, name = 'clearhtml';
+ self.clickToolbar(name, function() {
+ self.focus();
+ var html = self.html();
+ html = html.replace(/(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+