瀏覽代碼

Added a graph library

Sergei Solovev 1 年之前
父節點
當前提交
c68552a0fb
共有 100 個文件被更改,包括 28610 次插入26 次删除
  1. 2 1
      .gitignore
  2. 2 1
      composer.json
  3. 72 1
      composer.lock
  4. 0 4
      system/engine/graph.php
  5. 10 4
      system/library/cron/threads.php
  6. 14 11
      system/library/games/graph.php
  7. 0 4
      system/sections/servers/cs/graph.php
  8. 1 0
      vendor/composer/autoload_files.php
  9. 1 0
      vendor/composer/autoload_psr4.php
  10. 9 0
      vendor/composer/autoload_static.php
  11. 74 0
      vendor/composer/installed.json
  12. 9 0
      vendor/composer/installed.php
  13. 49 0
      vendor/szymach/c-pchart/.github/workflows/codacy-analysis.yml
  14. 6 0
      vendor/szymach/c-pchart/.gitignore
  15. 33 0
      vendor/szymach/c-pchart/.travis.yml
  16. 48 0
      vendor/szymach/c-pchart/CHANGELOG.md
  17. 675 0
      vendor/szymach/c-pchart/LICENSE
  18. 185 0
      vendor/szymach/c-pchart/README.md
  19. 1 0
      vendor/szymach/c-pchart/cache/.gitkeep
  20. 21 0
      vendor/szymach/c-pchart/codeception.yml
  21. 52 0
      vendor/szymach/c-pchart/composer.json
  22. 206 0
      vendor/szymach/c-pchart/constants.php
  23. 7 0
      vendor/szymach/c-pchart/coverage.sh
  24. 9 0
      vendor/szymach/c-pchart/docker-compose.yml
  25. 26 0
      vendor/szymach/c-pchart/phpcs.xml
  26. 107 0
      vendor/szymach/c-pchart/resources/barcode/128B.db
  27. 44 0
      vendor/szymach/c-pchart/resources/barcode/39.db
  28. 87 0
      vendor/szymach/c-pchart/resources/doc/2d_pie.md
  29. 72 0
      vendor/szymach/c-pchart/resources/doc/2d_ring.md
  30. 119 0
      vendor/szymach/c-pchart/resources/doc/3d_pie.md
  31. 78 0
      vendor/szymach/c-pchart/resources/doc/3d_ring.md
  32. 72 0
      vendor/szymach/c-pchart/resources/doc/area.md
  33. 64 0
      vendor/szymach/c-pchart/resources/doc/bar.md
  34. 89 0
      vendor/szymach/c-pchart/resources/doc/barcode_128.md
  35. 82 0
      vendor/szymach/c-pchart/resources/doc/barcode_39.md
  36. 70 0
      vendor/szymach/c-pchart/resources/doc/best_fit.md
  37. 107 0
      vendor/szymach/c-pchart/resources/doc/bubble.md
  38. 83 0
      vendor/szymach/c-pchart/resources/doc/cache.md
  39. 92 0
      vendor/szymach/c-pchart/resources/doc/contour.md
  40. 125 0
      vendor/szymach/c-pchart/resources/doc/filled_spline.md
  41. 75 0
      vendor/szymach/c-pchart/resources/doc/filled_step.md
  42. 50 0
      vendor/szymach/c-pchart/resources/doc/line.md
  43. 67 0
      vendor/szymach/c-pchart/resources/doc/plot.md
  44. 96 0
      vendor/szymach/c-pchart/resources/doc/polar.md
  45. 71 0
      vendor/szymach/c-pchart/resources/doc/progress.md
  46. 98 0
      vendor/szymach/c-pchart/resources/doc/radar.md
  47. 108 0
      vendor/szymach/c-pchart/resources/doc/scatter_best_fit.md
  48. 100 0
      vendor/szymach/c-pchart/resources/doc/scatter_line.md
  49. 107 0
      vendor/szymach/c-pchart/resources/doc/scatter_plot.md
  50. 108 0
      vendor/szymach/c-pchart/resources/doc/scatter_spline.md
  51. 116 0
      vendor/szymach/c-pchart/resources/doc/scatter_threshold.md
  52. 116 0
      vendor/szymach/c-pchart/resources/doc/scatter_threshold_area.md
  53. 37 0
      vendor/szymach/c-pchart/resources/doc/spline.md
  54. 74 0
      vendor/szymach/c-pchart/resources/doc/split_path.md
  55. 62 0
      vendor/szymach/c-pchart/resources/doc/spring.md
  56. 88 0
      vendor/szymach/c-pchart/resources/doc/stacked_area.md
  57. 87 0
      vendor/szymach/c-pchart/resources/doc/stacked_bar.md
  58. 93 0
      vendor/szymach/c-pchart/resources/doc/step.md
  59. 78 0
      vendor/szymach/c-pchart/resources/doc/stock.md
  60. 81 0
      vendor/szymach/c-pchart/resources/doc/surface.md
  61. 71 0
      vendor/szymach/c-pchart/resources/doc/zone.md
  62. 二進制
      vendor/szymach/c-pchart/resources/fonts/Bedizen.ttf
  63. 二進制
      vendor/szymach/c-pchart/resources/fonts/Forgotte.ttf
  64. 二進制
      vendor/szymach/c-pchart/resources/fonts/GeosansLight.ttf
  65. 二進制
      vendor/szymach/c-pchart/resources/fonts/MankSans.ttf
  66. 二進制
      vendor/szymach/c-pchart/resources/fonts/Silkscreen.ttf
  67. 二進制
      vendor/szymach/c-pchart/resources/fonts/advent_light.ttf
  68. 二進制
      vendor/szymach/c-pchart/resources/fonts/calibri.ttf
  69. 二進制
      vendor/szymach/c-pchart/resources/fonts/pf_arma_five.ttf
  70. 二進制
      vendor/szymach/c-pchart/resources/fonts/verdana.ttf
  71. 6 0
      vendor/szymach/c-pchart/resources/palettes/autumn.color
  72. 6 0
      vendor/szymach/c-pchart/resources/palettes/blind.color
  73. 6 0
      vendor/szymach/c-pchart/resources/palettes/evening.color
  74. 6 0
      vendor/szymach/c-pchart/resources/palettes/kitchen.color
  75. 7 0
      vendor/szymach/c-pchart/resources/palettes/light.color
  76. 6 0
      vendor/szymach/c-pchart/resources/palettes/navy.color
  77. 6 0
      vendor/szymach/c-pchart/resources/palettes/shade.color
  78. 6 0
      vendor/szymach/c-pchart/resources/palettes/spring.color
  79. 6 0
      vendor/szymach/c-pchart/resources/palettes/summer.color
  80. 274 0
      vendor/szymach/c-pchart/src/Barcode/Barcode128.php
  81. 296 0
      vendor/szymach/c-pchart/src/Barcode/Barcode39.php
  82. 1728 0
      vendor/szymach/c-pchart/src/BaseDraw.php
  83. 392 0
      vendor/szymach/c-pchart/src/Cache.php
  84. 532 0
      vendor/szymach/c-pchart/src/Chart/Bubble.php
  85. 377 0
      vendor/szymach/c-pchart/src/Chart/Indicator.php
  86. 2320 0
      vendor/szymach/c-pchart/src/Chart/Pie.php
  87. 1074 0
      vendor/szymach/c-pchart/src/Chart/Radar.php
  88. 2255 0
      vendor/szymach/c-pchart/src/Chart/Scatter.php
  89. 180 0
      vendor/szymach/c-pchart/src/Chart/Split.php
  90. 1190 0
      vendor/szymach/c-pchart/src/Chart/Spring.php
  91. 453 0
      vendor/szymach/c-pchart/src/Chart/Stock.php
  92. 414 0
      vendor/szymach/c-pchart/src/Chart/Surface.php
  93. 1318 0
      vendor/szymach/c-pchart/src/Data.php
  94. 10363 0
      vendor/szymach/c-pchart/src/Draw.php
  95. 748 0
      vendor/szymach/c-pchart/src/Image.php
  96. 0 0
      vendor/szymach/c-pchart/tests/_data/.gitkeep
  97. 二進制
      vendor/szymach/c-pchart/tests/_data/accept.png
  98. 11 0
      vendor/szymach/c-pchart/tests/_data/test_palette.txt
  99. 2 0
      vendor/szymach/c-pchart/tests/_output/.gitignore
  100. 42 0
      vendor/szymach/c-pchart/tests/_support/Helper/Unit.php

+ 2 - 1
.gitignore

@@ -1,2 +1,3 @@
 .idea
-.vscode
+.vscode
+composer.phar

+ 2 - 1
composer.json

@@ -3,6 +3,7 @@
     "symfony/polyfill": "1.28.0",
     "filp/whoops": "2.15.4",
     "monolog/monolog": "2.9.2",
-    "xpaw/php-source-query-class": "2.1.0"
+    "xpaw/php-source-query-class": "2.1.0",
+    "szymach/c-pchart": "3.0.17"
   }
 }

+ 72 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "567327add4f8b463f2486e21cc511bea",
+    "content-hash": "9fe541d20000bad8bcc7a6f093bd89a1",
     "packages": [
         {
             "name": "filp/whoops",
@@ -343,6 +343,77 @@
             ],
             "time": "2023-08-25T17:27:34+00:00"
         },
+        {
+            "name": "szymach/c-pchart",
+            "version": "v3.0.17",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/szymach/c-pchart.git",
+                "reference": "c022568da78f73bb7acf7db7a974934a434d47d7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/szymach/c-pchart/zipball/c022568da78f73bb7acf7db7a974934a434d47d7",
+                "reference": "c022568da78f73bb7acf7db7a974934a434d47d7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "php": "^5.4|^7.0|^8.0"
+            },
+            "require-dev": {
+                "codeception/codeception": "^4.1.22",
+                "codeception/module-asserts": "^1.3",
+                "codeception/module-filesystem": "^1.0",
+                "phpunit/phpunit": "^5.7|^9.5",
+                "squizlabs/php_codesniffer": "^3.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev",
+                    "2.0": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "constants.php"
+                ],
+                "psr-4": {
+                    "CpChart\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "Jean-Damien Pogolotti",
+                    "homepage": "http://www.pchart.net",
+                    "role": "Creator of the original pChart library"
+                },
+                {
+                    "name": "Piotr Szymaszek",
+                    "homepage": "https://github.com/szymach",
+                    "role": "Developer of the CpChart wrapper package"
+                }
+            ],
+            "description": "Port of \"pChart\" library into PHP 5+",
+            "homepage": "https://github.com/szymach/c-pchart",
+            "keywords": [
+                "CpChart",
+                "c-pChart",
+                "charts",
+                "pchart",
+                "statistics"
+            ],
+            "support": {
+                "issues": "https://github.com/szymach/c-pchart/issues",
+                "source": "https://github.com/szymach/c-pchart/tree/v3.0.17"
+            },
+            "time": "2023-05-27T11:12:02+00:00"
+        },
         {
             "name": "xpaw/php-source-query-class",
             "version": "2.1.0",

+ 0 - 4
system/engine/graph.php

@@ -14,10 +14,6 @@ if (!$sql->num())
 
 $graph = $sql->get();
 
-include(LIB . 'games/graph/pData.php');
-include(LIB . 'games/graph/pDraw.php');
-include(LIB . 'games/graph/pImage.php');
-
 if (isset($url['type'])) {
     include(DATA . 'graph.php');
 

+ 10 - 4
system/library/cron/threads.php

@@ -15,7 +15,7 @@ class threads extends cron
             return NULL;
 
         while ($unit = $sql->get())
-            $aUnit[$unit['id']] = '';
+            $aUnit[$unit['id']] = [];
 
         $sql->query('SELECT `id` FROM `servers` LIMIT 1');
 
@@ -26,14 +26,19 @@ class threads extends cron
 
         $all = $sql->num();
 
-        while ($server = $sql->get())
-            $aUnit[$server['unit']][$server['game']] = $server['id'] . ' ';
+        while($server = $sql->get()) {
+            $aUnit[$server['unit']][$server['game']] ??= [];
+            $aUnit[$server['unit']][$server['game']][] = $server['id'];
+        }
 
         if ($argv[3] == 'scan_servers_route')
             cron::$seping = 50;
 
         foreach ($aUnit as $unit => $aGame) {
             foreach ($aGame as $game => $servers) {
+                if(is_array($servers)) {
+                    $servers = implode(' ', $servers);
+                }
                 $aData = explode(' ', $servers);
 
                 $num = count($aData) - 1;
@@ -49,9 +54,10 @@ class threads extends cron
 
         foreach ($threads as $thread) {
             foreach ($thread as $screen => $servers)
-                $cmd .= 'sudo -u www-data screen -dmS scan_' . (sys::first(explode(' ', $servers))) . '_' . $screen . ' taskset -c ' . $cfg['cron_taskset'] . ' sh -c \"cd /var/enginegp; php cron.php ' . $cfg['cron_key'] . ' ' . $argv[3] . ' ' . $servers . '\"; sleep 1;';
+                $cmd .= 'sudo -u www-data screen -dmS scan_' . (sys::first(explode(' ', $servers))) . '_' . $screen . ' taskset -c ' . $cfg['cron_taskset'] . ' sh -c \"cd /var/www/enginegp; php7.4 cron.php ' . $cfg['cron_key'] . ' ' . $argv[3] . ' ' . $servers . '\"; sleep 1;';
         }
 
+        $start_point = $_SERVER['REQUEST_TIME'];
         exec('screen -dmS threads_' . date('His', $start_point) . ' sh -c "' . $cmd . '"');
 
         return NULL;

+ 14 - 11
system/library/games/graph.php

@@ -2,6 +2,9 @@
 if (!DEFINED('EGP'))
     exit(header('Refresh: 0; URL=http://' . $_SERVER['SERVER_NAME'] . '/404'));
 
+use CpChart\Data;
+use CpChart\Image;
+
 class graph
 {
     public static function full($server, $slots, $key, $time)
@@ -15,7 +18,7 @@ class graph
         $aRAM = $aData['ram'];
         $aHDD = $aData['hdd'];
 
-        $MyData = new pData();
+        $MyData = new Data();
 
         // Онлайн
         $MyData->addPoints($aOnline, 'ONLINE');
@@ -51,7 +54,7 @@ class graph
         $MyData->setPalette('RAM', array('R' => 26, 'G' => 150, 'B' => 38));
         $MyData->setPalette('HDD', array('R' => 205, 'G' => 196, 'B' => 37));
 
-        $myPicture = new pImage(896, 220, $MyData);
+        $myPicture = new Image(896, 220, $MyData);
 
         $myPicture->drawFilledRectangle(0, 0, 896, 220, array('R' => 255, 'G' => 255, 'B' => 255));
 
@@ -60,17 +63,17 @@ class graph
         $myPicture->setFontProperties(array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/tahoma.ttf', 'FontSize' => 8));
         $myPicture->setGraphArea(40, 20, 616, 190);
         $myPicture->drawFilledRectangle(40, 20, 616, 190, array('R' => 240, 'G' => 242, 'B' => 242, 'Alpha' => 100));
-        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'GridR' => 76, 'GridG' => 109, 'GridB' => 120, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'Factors' => array(0 => array($slots), 1 => array(25, 50, 75, 100)), 'ManualScale' => array(0 => array('Min' => 0, 'Max' => $slots), 1 => array('Min' => 0, 'Max' => 100))));
+        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'GridR' => 76, 'GridG' => 109, 'GridB' => 120, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'ManualScale' => array(0 => array('Min' => 0, 'Max' => 32), 1 => array('Min' => 0, 'Max' => 100))));
 
         $myPicture->drawText(676, 34, 'Средний онлайн: ' . graph::average($aOnline), array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
         $myPicture->drawText(676, 54, 'Средняя нагрузка (CPU): ' . graph::average($aCPU) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
         $myPicture->drawText(676, 74, 'Средняя нагрузка (RAM): ' . graph::average($aRAM) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
-        $myPicture->drawText(676, 94, 'Средняя нагрузка (HDD): ' . graph::average($aHDD) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
+        $myPicture->drawText(676, 94, 'Средняя нагрузка (HDD): ' . graph::average($aHDD), array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
 
         $myPicture->drawText(676, 129, 'Максимальный онлайн: ' . max($aOnline), array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
         $myPicture->drawText(676, 153, 'Максимальная нагрузка (CPU): ' . max($aCPU) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
         $myPicture->drawText(676, 173, 'Максимальная нагрузка (RAM): ' . max($aRAM) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
-        $myPicture->drawText(676, 193, 'Максимальная нагрузка (HDD): ' . max($aHDD) . '%', array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
+        $myPicture->drawText(676, 193, 'Максимальная нагрузка (HDD): ' . max($aHDD), array('R' => 25, 'G' => 25, 'B' => 25, 'FontName' => LIB . 'games/graph/fonts/arianamu.ttf', 'FontSize' => 10, 'Align' => TEXT_ALIGN_BOTTOMLEFT));
 
         $myPicture->setFontProperties(array('FontName' => LIB . 'games/graph/fonts/tahoma.ttf', 'FontSize' => 7));
         $myPicture->drawSplineChart();
@@ -89,7 +92,7 @@ class graph
     {
         global $cfg, $aGname;
 
-        $MyData = new pData();
+        $MyData = new Data();
 
         // Значения
         $MyData->addPoints($aPoints, 'ONLINE');
@@ -101,7 +104,7 @@ class graph
         $MyData->setPalette('ONLINE', $aStyle[$style]['line']);
 
         // Размер баннера
-        $myPicture = new pImage(160, 248, $MyData);
+        $myPicture = new Image(160, 248, $MyData);
 
         // Цвет фона
         $myPicture->drawFilledRectangle(0, 0, 160, 248, $aStyle[$style]['fon']);
@@ -119,7 +122,7 @@ class graph
         $myPicture->drawFilledRectangle(35, 160, 150, 210, $aStyle[$style]['graph']);
 
         // График
-        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'CycleBackground' => TRUE, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'Factors' => array(0 => array($server['slots_start'])), 'ManualScale' => array(0 => array('Min' => 0, 'Max' => $server['slots_start']))));
+        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'CycleBackground' => TRUE, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'Factors' => array($server['slots_start']), 'ManualScale' => array(0 => array('Min' => 0, 'Max' => $server['slots_start']))));
 
         // Название игрового сервера
         $myPicture->drawFilledRectangle(0, 0, 18, 248, $aStyle[$style]['leftbox']);
@@ -166,7 +169,7 @@ class graph
     {
         global $cfg, $aGname;
 
-        $MyData = new pData();
+        $MyData = new Data();
 
         // Значения
         $MyData->addPoints($aPoints, 'ONLINE');
@@ -178,7 +181,7 @@ class graph
         $MyData->setPalette('ONLINE', $aStyle[$style]['line']);
 
         // Размер баннера
-        $myPicture = new pImage(560, 95, $MyData);
+        $myPicture = new Image(560, 95, $MyData);
 
         // Цвет фона
         $myPicture->drawFilledRectangle(0, 0, 560, 95, $aStyle[$style]['fon']);
@@ -204,7 +207,7 @@ class graph
         $myPicture->drawFilledRectangle(430, 5, 554, 60, $aStyle[$style]['graph']);
 
         // График
-        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'CycleBackground' => TRUE, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'Factors' => array(0 => array($server['slots_start'])), 'ManualScale' => array(0 => array('Min' => 0, 'Max' => $server['slots_start']))));
+        $myPicture->drawScale(array('XMargin' => 5, 'YMargin' => 5, 'CycleBackground' => TRUE, 'LabelSkip' => 0, 'DrawSubTicks' => TRUE, 'Mode' => SCALE_MODE_MANUAL, 'Factors' => array($server['slots_start']), 'ManualScale' => array(0 => array('Min' => 0, 'Max' => $server['slots_start']))));
 
         // Адрес игрового сервера
         $myPicture->drawFilledRectangle(5, 36, 210, 49, $aStyle[$style]['box']);

+ 0 - 4
system/sections/servers/cs/graph.php

@@ -19,10 +19,6 @@ if ($go) {
         exit(file_get_contents(TEMP . (md5($graph['key'] . 'full_' . $time)) . '.png'));
     }
 
-    include(LIB . 'games/graph/pData.php');
-    include(LIB . 'games/graph/pDraw.php');
-    include(LIB . 'games/graph/pImage.php');
-
     include(LIB . 'games/graph.php');
 
     graph::full($id, $server['slots_start'], $graph['key'], $time);

+ 1 - 0
vendor/composer/autoload_files.php

@@ -17,4 +17,5 @@ return array(
     'a4e34e0535907b5c234b9abc547237ca' => $vendorDir . '/symfony/polyfill/src/Intl/MessageFormatter/bootstrap.php',
     '299b3c040b39cb03c6eceb9bb272ad1d' => $vendorDir . '/symfony/polyfill/src/Intl/Normalizer/bootstrap.php',
     'e59f725579f9974327c76777296d6dcc' => $vendorDir . '/symfony/polyfill/src/Mbstring/bootstrap.php',
+    '7bb4f001eb5212bde073bf47a4bbedad' => $vendorDir . '/szymach/c-pchart/constants.php',
 );

+ 1 - 0
vendor/composer/autoload_psr4.php

@@ -11,4 +11,5 @@ return array(
     'Symfony\\Polyfill\\' => array($vendorDir . '/symfony/polyfill/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
     'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
+    'CpChart\\' => array($vendorDir . '/szymach/c-pchart/src'),
 );

+ 9 - 0
vendor/composer/autoload_static.php

@@ -18,6 +18,7 @@ class ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4
         'a4e34e0535907b5c234b9abc547237ca' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/MessageFormatter/bootstrap.php',
         '299b3c040b39cb03c6eceb9bb272ad1d' => __DIR__ . '/..' . '/symfony/polyfill/src/Intl/Normalizer/bootstrap.php',
         'e59f725579f9974327c76777296d6dcc' => __DIR__ . '/..' . '/symfony/polyfill/src/Mbstring/bootstrap.php',
+        '7bb4f001eb5212bde073bf47a4bbedad' => __DIR__ . '/..' . '/szymach/c-pchart/constants.php',
     );
 
     public static $prefixLengthsPsr4 = array (
@@ -41,6 +42,10 @@ class ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4
         array (
             'Monolog\\' => 8,
         ),
+        'C' => 
+        array (
+            'CpChart\\' => 8,
+        ),
     );
 
     public static $prefixDirsPsr4 = array (
@@ -64,6 +69,10 @@ class ComposerStaticInit76468c2eb232fdf4a8e3502b319270a4
         array (
             0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
         ),
+        'CpChart\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/szymach/c-pchart/src',
+        ),
     );
 
     public static $classMap = array (

+ 74 - 0
vendor/composer/installed.json

@@ -349,6 +349,80 @@
             ],
             "install-path": "../symfony/polyfill"
         },
+        {
+            "name": "szymach/c-pchart",
+            "version": "v3.0.17",
+            "version_normalized": "3.0.17.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/szymach/c-pchart.git",
+                "reference": "c022568da78f73bb7acf7db7a974934a434d47d7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/szymach/c-pchart/zipball/c022568da78f73bb7acf7db7a974934a434d47d7",
+                "reference": "c022568da78f73bb7acf7db7a974934a434d47d7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "php": "^5.4|^7.0|^8.0"
+            },
+            "require-dev": {
+                "codeception/codeception": "^4.1.22",
+                "codeception/module-asserts": "^1.3",
+                "codeception/module-filesystem": "^1.0",
+                "phpunit/phpunit": "^5.7|^9.5",
+                "squizlabs/php_codesniffer": "^3.4"
+            },
+            "time": "2023-05-27T11:12:02+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev",
+                    "2.0": "2.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "constants.php"
+                ],
+                "psr-4": {
+                    "CpChart\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "Jean-Damien Pogolotti",
+                    "homepage": "http://www.pchart.net",
+                    "role": "Creator of the original pChart library"
+                },
+                {
+                    "name": "Piotr Szymaszek",
+                    "homepage": "https://github.com/szymach",
+                    "role": "Developer of the CpChart wrapper package"
+                }
+            ],
+            "description": "Port of \"pChart\" library into PHP 5+",
+            "homepage": "https://github.com/szymach/c-pchart",
+            "keywords": [
+                "CpChart",
+                "c-pChart",
+                "charts",
+                "pchart",
+                "statistics"
+            ],
+            "support": {
+                "issues": "https://github.com/szymach/c-pchart/issues",
+                "source": "https://github.com/szymach/c-pchart/tree/v3.0.17"
+            },
+            "install-path": "../szymach/c-pchart"
+        },
         {
             "name": "xpaw/php-source-query-class",
             "version": "2.1.0",

+ 9 - 0
vendor/composer/installed.php

@@ -175,6 +175,15 @@
                 0 => 'v1.28.0',
             ),
         ),
+        'szymach/c-pchart' => array(
+            'pretty_version' => 'v3.0.17',
+            'version' => '3.0.17.0',
+            'reference' => 'c022568da78f73bb7acf7db7a974934a434d47d7',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../szymach/c-pchart',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
         'xpaw/php-source-query-class' => array(
             'pretty_version' => '2.1.0',
             'version' => '2.1.0.0',

+ 49 - 0
vendor/szymach/c-pchart/.github/workflows/codacy-analysis.yml

@@ -0,0 +1,49 @@
+# This workflow checks out code, performs a Codacy security scan
+# and integrates the results with the
+# GitHub Advanced Security code scanning feature.  For more information on
+# the Codacy security scan action usage and parameters, see
+# https://github.com/codacy/codacy-analysis-cli-action.
+# For more information on Codacy Analysis CLI in general, see
+# https://github.com/codacy/codacy-analysis-cli.
+
+name: Codacy Security Scan
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '45 3 * * 0'
+
+jobs:
+  codacy-security-scan:
+    name: Codacy Security Scan
+    runs-on: ubuntu-latest
+    steps:
+      # Checkout the repository to the GitHub Actions runner
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
+      - name: Run Codacy Analysis CLI
+        uses: codacy/codacy-analysis-cli-action@1.1.0
+        with:
+          # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
+          # You can also omit the token and run the tools that support default configurations
+          project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
+          verbose: true
+          output: results.sarif
+          format: sarif
+          # Adjust severity of non-security issues
+          gh-code-scanning-compat: true
+          # Force 0 exit code to allow SARIF file generation
+          # This will handover control about PR rejection to the GitHub side
+          max-allowed-issues: 2147483647
+
+      # Upload the SARIF file generated in the previous step
+      - name: Upload SARIF results file
+        uses: github/codeql-action/upload-sarif@v1
+        with:
+          sarif_file: results.sarif

+ 6 - 0
vendor/szymach/c-pchart/.gitignore

@@ -0,0 +1,6 @@
+.phpcs-cache
+composer.phar
+composer.lock
+cache/*
+!cache/.gitkeep
+vendor

+ 33 - 0
vendor/szymach/c-pchart/.travis.yml

@@ -0,0 +1,33 @@
+language: php
+
+cache:
+  directories:
+    - vendor
+
+matrix:
+    include:
+        - php: 5.6
+          env:
+            - COMPOSER_FLAGS='--prefer-lowest'
+        - php: 7.0
+        - php: 7.1
+        - php: 7.2
+        - php: 7.3
+        - php: 7.4
+        - php: 8.0
+        - php: 8.1
+          env:
+            - COVERAGE='--coverage --coverage-xml'
+              XDEBUG_MODE=coverage
+
+before_install:
+    - if [[ ! $COVERAGE ]]; then phpenv config-rm xdebug.ini; fi;
+
+before_script: composer update -n $COMPOSER_FLAGS
+
+script:
+    - vendor/bin/phpcs
+    - vendor/bin/codecept run unit $COVERAGE
+
+after_script:
+    - if [[ $COVERAGE ]]; then ./coverage.sh; fi;

+ 48 - 0
vendor/szymach/c-pchart/CHANGELOG.md

@@ -0,0 +1,48 @@
+# Changelog
+
+## 1.x
+1.0 Stable version with basic functionality.
+
+1.1 Added factory service.
+
+1.1.1 Changed chart loading via factory a bit (see class annotations).
+
+1.1.2 Updated service class with Exception handling regarding missing / wrong class name.
+
+1.1.3 The file with classes' constants is now loaded via Composer (thanks to ThaDafinser).
+
+1.1.4 Fixed code-breaking typ (thanks to subtronic).
+
+1.1.5 Added an option to hide the X axis or only it's values (thanks to julien-gm).
+
+1.1.6 Added support for closures in formatting scale (thanks to funkjedi)
+
+## 2.x
+2.0 Updated all classes to PSR-2 standard, added typehinting where possible, updated
+    annotations in methods to be as accurate as possible. Added Behat testing and
+    restructed the namespaces into more sensible structure.
+
+2.0.1 Documentation updates.
+
+2.0.2 Changed license to GPL-3.0.
+
+2.0.3 Bubble chart fix (thanks to rage28).
+
+2.0.4 PHP 7.1 initial support, lowered minimal PHP version to 5.4.
+
+2.0.5 CS fixes, removed old MIT license file.
+
+2.0.6 A fix for PHP 7.1 (thanks to dehrk).
+
+2.0.7 A fix for computing color alpha (thanks to dehrk).
+
+2.0.8 Covered most basic functionality with tests, added a lot of documentation
+      and a PHP 7.1 fix. Deprecated the `\CpChart\Factory\Factory` class.
+
+## 3.x
+
+3.0 Deleted the `\CpChart\Factory\Factory` class and everything related to it.
+    Moved drawing and cache classes outside of `Chart` namespace.
+    Moved barcode to a `Barcode` namespace.
+    Moved `cache` and `resources` directories to library root.
+    Renamed `resources\data` to `resources\barcode`.

+ 675 - 0
vendor/szymach/c-pchart/LICENSE

@@ -0,0 +1,675 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+

+ 185 - 0
vendor/szymach/c-pchart/README.md

@@ -0,0 +1,185 @@
+Table of contents:
+==================
+* [Support](#support)
+* [Build status](#build-status)
+* [Code quality](#code-quality)
+* [About](#about)
+* [License](#license)
+* [Contributing](#contributing)
+* [Installation](#installation-via-composer)
+* [Usage](#usage)
+    - [Charts created through Image class](#charts-created-through-image-class)
+    - [Standalone charts](#standalone-charts)
+    - [Barcodes](#barcodes)
+    - [Cache](#cache)
+    - [Fonts and palletes](#fonts-and-palletes)
+* [Changelog](#changelog)
+* [References](#references)
+* [Links](#links)
+
+Support:
+========
+
+This project is supported in a basic manner and no new features will be introduced.
+Issues and pull requests will be reviewed and resolved if need be, so feel free
+to post them.
+
+Build status:
+=============
+- [![Build Status](https://app.travis-ci.com/szymach/c-pchart.svg?branch=master)](https://app.travis-ci.com/szymach/c-pchart) master
+- [![Build Status](https://app.travis-ci.com/szymach/c-pchart.svg?branch=3.0)](https://app.travis-ci.com/szymach/c-pchart) 3.0
+- [![Build Status](https://app.travis-ci.com/szymach/c-pchart.svg?branch=2.0)](https://app.travis-ci.com/szymach/c-pchart) 2.0
+
+About:
+======
+
+This library is a port of the excellent pChart statistics library created by Jean-Damien Pogolotti,
+and aims to allow the usage of it in modern applications. This was done through
+applying PSR standards to code, introducing namespaces and typehints, along with
+some basic annotations to methods.
+
+This is the `3.x` version, which removes the factory service and reorganizes the
+file structure a bit. It does not introduce any new features, but the changes are
+not compatibile with the `2.x` branch. BC compatibility with the original library
+is mostly retained, however you can still use the `1.x` version if you cannot risk
+any of these.
+
+What was done:
+
+- Support for PHP versions from 5.4 to 8.1.
+
+- Made a full port of the library's functionality. I have touched very little of
+the actual logic, so most code from the original library should work.
+
+- Defined and added namespaces to all classes.
+
+- Replaced all `exit()` / `die()` commands with `throw` statements.
+
+- Refactored the code to meet PSR-2 standard and added annotations (as best as I could figure them out)
+to methods Also, typehinting was added to methods where possible, so some backwards compatibility breaks
+may occur if you did some weird things.
+
+- Moved all constants to a [single file](constants.php). It is loaded automatically
+through Composer, so no need for manual action.
+
+License:
+========
+
+It was previously stated that this package uses the [MIT](https://opensource.org/licenses/MIT) license,
+which did not meet the requirements set by the original author. It is now under the
+[GNU GPL v3](http://www.gnu.org/licenses/gpl-3.0.html) license, so if you wish to
+use it in a commercial project, you need to pay an [appropriate fee](http://www.pchart.net/license).
+
+Contributing:
+=============
+
+All in all, this is a legacy library ported over from PHP 4, so the code is neither
+beautiful nor easy to understand. I did my best to modernize and cover it with
+some basic tests, but there is much more that could be done. If you are willing and
+have time to fix or improve anything, feel free to post a PR or issue.
+
+Installation (via Composer):
+============================
+
+For composer installation, add:
+
+```json
+"require": {
+    "szymach/c-pchart": "^3.0"
+},
+```
+
+to your composer.json file and update your dependencies. Or you can run:
+
+```sh
+$ composer require szymach/c-pchart
+```
+
+in your project's root directory.
+
+Usage:
+======
+
+Your best source to understanding how to use the library is still the [official wiki](http://wiki.pchart.net/).
+However, I have ported at least one example for each chart into Markdown files,
+so you can compare each version and figure out how to use the current implementation.
+
+Charts created through Image class
+---------------------------------------
+
+Most of the basic charts are created through methods of the `CpChart\Image`
+class. Below you can find a full list of these charts, alongside example code.
+
+- [area](resources/doc/area.md)
+- [bar](resources/doc/bar.md)
+- [best fit](resources/doc/best_fit.md)
+- [filled spline](resources/doc/filled_spline.md)
+- [filled step](resources/doc/filled_step.md)
+- [line](resources/doc/line.md)
+- [plot](resources/doc/plot.md)
+- [progress](resources/doc/progress.md)
+- [spline](resources/doc/spline.md)
+- [stacked area](resources/doc/stacked_area.md)
+- [stacked bar](resources/doc/stacked_bar.md)
+- [step](resources/doc/step.md)
+- [zone](resources/doc/zone.md)
+
+Standalone charts:
+------------------------------------
+
+The more advanced charts have their own separate class you need to use in order
+to create them. As before, below is a full list of these, with example code.
+
+- [2D pie](resources/doc/2d_pie.md)
+- [3D pie](resources/doc/3d_pie.md)
+- [2D ring](resources/doc/2d_ring.md)
+- [3D ring](resources/doc/3d_ring.md)
+- [bubble](resources/doc/bubble.md)
+- [contour](resources/doc/contour.md)
+- [polar](resources/doc/polar.md)
+- [radar](resources/doc/radar.md)
+- [scatter best fit](resources/doc/scatter_best_fit.md)
+- [scatter line](resources/doc/scatter_line.md)
+- [scatter plot](resources/doc/scatter_plot.md)
+- [scatter spline](resources/doc/scatter_spline.md)
+- [scatter threshold](resources/doc/scatter_threshold.md)
+- [scatter threshold area](resources/doc/scatter_threshold_area.md)
+- [split path](resources/doc/split_path.md)
+- [spring](resources/doc/spring.md)
+- [stock](resources/doc/stock.md)
+- [surface](resources/doc/surface.md)
+
+Barcodes
+--------
+
+The pChart library also provides a way to render barcodes 39 and 128. Below you
+can find links to doc on creating them:
+
+- [barcode39](resources/doc/barcode_39.md)
+- [barcode128](resources/doc/barcode_128.md)
+
+Cache
+-----
+
+If you find yourself creating charts out of a set of data more than once, you may
+consider using the cache component of the library. Head on to the [dedicated part](resources/doc/cache.md)
+of the documentation for information on how to do that.
+
+Fonts and palletes
+------------------
+
+If you want to use any of the fonts or palletes files, provide only
+the name of the actual file, do not add the `fonts` or `palettes` folder to the
+string given into the function. If you want to load them from a different directory
+than the default, you need to add the full path to the file (ex. `__DIR__.'/folder/to/my/palletes`).
+
+References
+==========
+[The original pChart website](http://www.pchart.net/)
+
+Links
+=====
+
+[GitHub](https://github.com/szymach/c-pchart)
+
+[Packagist](https://packagist.org/packages/szymach/c-pchart)

+ 1 - 0
vendor/szymach/c-pchart/cache/.gitkeep

@@ -0,0 +1 @@
+

+ 21 - 0
vendor/szymach/c-pchart/codeception.yml

@@ -0,0 +1,21 @@
+---
+namespace: Test\CpChart
+paths:
+    tests: tests
+    output: tests/_output
+    data: tests/_data
+    support: tests/_support
+actor_suffix: Tester
+extensions:
+    enabled:
+        - Codeception\Extension\RunFailed
+coverage:
+    enabled: true
+    include:
+        - src/*
+settings:
+    be_strict_about_changes_to_global_state: true
+    convert_deprecations_to_exceptions: true
+    colors: true
+    error_level: E_ALL | E_STRICT | E_DEPRECATED
+    report_useless_tests: true

+ 52 - 0
vendor/szymach/c-pchart/composer.json

@@ -0,0 +1,52 @@
+{
+    "name": "szymach/c-pchart",
+    "license": "GPL-3.0-only",
+    "type": "library",
+    "description": "Port of \"pChart\" library into PHP 5+",
+    "keywords": ["pchart", "pChart", "statistics", "charts", "CpChart", "c-pChart"],
+    "homepage": "https://github.com/szymach/c-pchart",
+    "authors": [
+        {
+            "name": "Jean-Damien Pogolotti",
+            "homepage": "http://www.pchart.net",
+            "role": "Creator of the original pChart library"
+        },
+        {
+            "name": "Piotr Szymaszek",
+            "homepage": "https://github.com/szymach",
+            "role": "Developer of the CpChart wrapper package"
+        }
+    ],
+    "autoload": {
+        "psr-4": { "CpChart\\": "src/" },
+        "files": ["constants.php"]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Test\\CpChart\\": [
+                "tests/unit/",
+                "tests/_support/"
+            ]
+        }
+    },
+    "require": {
+        "php": "^5.4|^7.0|^8.0",
+        "ext-gd": "*"
+    },
+    "require-dev" : {
+        "codeception/codeception": "^4.1.22",
+        "phpunit/phpunit": "^5.7|^9.5",
+        "squizlabs/php_codesniffer": "^3.4",
+        "codeception/module-asserts": "^1.3",
+        "codeception/module-filesystem": "^1.0"
+    },
+    "config": {
+        "bin-dir": "vendor/bin"
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "3.0.x-dev",
+            "2.0": "2.0.x-dev"
+        }
+    }
+}

+ 206 - 0
vendor/szymach/c-pchart/constants.php

@@ -0,0 +1,206 @@
+<?php
+
+/**
+ * @filesource src/Resources/data/configuration.php
+ *
+ * All the class constants that were previously in class files are moved here into a single file.
+ */
+/**
+ * pBubble
+ */
+define("BUBBLE_SHAPE_ROUND", 700001);
+define("BUBBLE_SHAPE_SQUARE", 700002);
+
+/**
+ * pData
+ */
+/* Axis configuration */
+define("AXIS_FORMAT_DEFAULT", 680001);
+define("AXIS_FORMAT_TIME", 680002);
+define("AXIS_FORMAT_DATE", 680003);
+define("AXIS_FORMAT_METRIC", 680004);
+define("AXIS_FORMAT_CURRENCY", 680005);
+define("AXIS_FORMAT_TRAFFIC", 680006);
+define("AXIS_FORMAT_CUSTOM", 680007);
+/* Axis position */
+define("AXIS_POSITION_LEFT", 681001);
+define("AXIS_POSITION_RIGHT", 681002);
+define("AXIS_POSITION_TOP", 681001);
+define("AXIS_POSITION_BOTTOM", 681002);
+/* Families of data points */
+define("SERIE_SHAPE_FILLEDCIRCLE", 681011);
+define("SERIE_SHAPE_FILLEDTRIANGLE", 681012);
+define("SERIE_SHAPE_FILLEDSQUARE", 681013);
+define("SERIE_SHAPE_FILLEDDIAMOND", 681017);
+define("SERIE_SHAPE_CIRCLE", 681014);
+define("SERIE_SHAPE_TRIANGLE", 681015);
+define("SERIE_SHAPE_SQUARE", 681016);
+define("SERIE_SHAPE_DIAMOND", 681018);
+/* Axis position */
+define("AXIS_X", 682001);
+define("AXIS_Y", 682002);
+/* Define value limits */
+define("ABSOLUTE_MIN", -10000000000000);
+define("ABSOLUTE_MAX", 10000000000000);
+/* Replacement to the PHP null keyword */
+define("VOID", 0.123456789);
+/* Euro symbol for GD fonts */
+define("EURO_SYMBOL", "&#8364;");
+
+/**
+ * pDraw
+ */
+define("DIRECTION_VERTICAL", 690001);
+define("DIRECTION_HORIZONTAL", 690002);
+define("SCALE_POS_LEFTRIGHT", 690101);
+define("SCALE_POS_TOPBOTTOM", 690102);
+define("SCALE_MODE_FLOATING", 690201);
+define("SCALE_MODE_START0", 690202);
+define("SCALE_MODE_ADDALL", 690203);
+define("SCALE_MODE_ADDALL_START0", 690204);
+define("SCALE_MODE_MANUAL", 690205);
+define("SCALE_SKIP_NONE", 690301);
+define("SCALE_SKIP_SAME", 690302);
+define("SCALE_SKIP_NUMBERS", 690303);
+define("TEXT_ALIGN_TOPLEFT", 690401);
+define("TEXT_ALIGN_TOPMIDDLE", 690402);
+define("TEXT_ALIGN_TOPRIGHT", 690403);
+define("TEXT_ALIGN_MIDDLELEFT", 690404);
+define("TEXT_ALIGN_MIDDLEMIDDLE", 690405);
+define("TEXT_ALIGN_MIDDLERIGHT", 690406);
+define("TEXT_ALIGN_BOTTOMLEFT", 690407);
+define("TEXT_ALIGN_BOTTOMMIDDLE", 690408);
+define("TEXT_ALIGN_BOTTOMRIGHT", 690409);
+define("POSITION_TOP", 690501);
+define("POSITION_BOTTOM", 690502);
+define("LABEL_POS_LEFT", 690601);
+define("LABEL_POS_CENTER", 690602);
+define("LABEL_POS_RIGHT", 690603);
+define("LABEL_POS_TOP", 690604);
+define("LABEL_POS_BOTTOM", 690605);
+define("LABEL_POS_INSIDE", 690606);
+define("LABEL_POS_OUTSIDE", 690607);
+define("ORIENTATION_HORIZONTAL", 690701);
+define("ORIENTATION_VERTICAL", 690702);
+define("ORIENTATION_AUTO", 690703);
+define("LEGEND_NOBORDER", 690800);
+define("LEGEND_BOX", 690801);
+define("LEGEND_ROUND", 690802);
+define("LEGEND_VERTICAL", 690901);
+define("LEGEND_HORIZONTAL", 690902);
+define("LEGEND_FAMILY_BOX", 691051);
+define("LEGEND_FAMILY_CIRCLE", 691052);
+define("LEGEND_FAMILY_LINE", 691053);
+define("DISPLAY_AUTO", 691001);
+define("DISPLAY_MANUAL", 691002);
+define("LABELING_ALL", 691011);
+define("LABELING_DIFFERENT", 691012);
+define("BOUND_MIN", 691021);
+define("BOUND_MAX", 691022);
+define("BOUND_BOTH", 691023);
+define("BOUND_LABEL_POS_TOP", 691031);
+define("BOUND_LABEL_POS_BOTTOM", 691032);
+define("BOUND_LABEL_POS_AUTO", 691033);
+define("CAPTION_LEFT_TOP", 691041);
+define("CAPTION_RIGHT_BOTTOM", 691042);
+define("GRADIENT_SIMPLE", 691051);
+define("GRADIENT_EFFECT_CAN", 691052);
+define("LABEL_TITLE_NOBACKGROUND", 691061);
+define("LABEL_TITLE_BACKGROUND", 691062);
+define("LABEL_POINT_NONE", 691071);
+define("LABEL_POINT_CIRCLE", 691072);
+define("LABEL_POINT_BOX", 691073);
+define("ZONE_NAME_ANGLE_AUTO", 691081);
+define("PI", 3.14159265);
+define("ALL", 69);
+define("NONE", 31);
+define("AUTO", 690000);
+define("OUT_OF_SIGHT", -10000000000000);
+
+/**
+ * pImage
+ */
+/* Image map handling */
+define("IMAGE_MAP_STORAGE_FILE", 680001);
+define("IMAGE_MAP_STORAGE_SESSION", 680002);
+/* Last generated chart layout */
+define("CHART_LAST_LAYOUT_REGULAR", 680011);
+define("CHART_LAST_LAYOUT_STACKED", 680012);
+/* ImageMap string delimiter */
+define("IMAGE_MAP_DELIMITER", chr(1));
+
+/**
+ * pIndicator
+ */
+define("INDICATOR_CAPTION_DEFAULT", 700001);
+define("INDICATOR_CAPTION_EXTENDED", 700002);
+define("INDICATOR_CAPTION_INSIDE", 700011);
+define("INDICATOR_CAPTION_BOTTOM", 700012);
+define("INDICATOR_VALUE_BUBBLE", 700021);
+define("INDICATOR_VALUE_LABEL", 700022);
+
+/**
+ * pPie
+ */
+/* Class return codes */
+define("PIE_NO_ABSCISSA", 140001);
+define("PIE_NO_DATASERIE", 140002);
+define("PIE_SUMISNULL", 140003);
+define("PIE_RENDERED", 140000);
+define("PIE_LABEL_COLOR_AUTO", 140010);
+define("PIE_LABEL_COLOR_MANUAL", 140011);
+define("PIE_VALUE_NATURAL", 140020);
+define("PIE_VALUE_PERCENTAGE", 140021);
+define("PIE_VALUE_INSIDE", 140030);
+define("PIE_VALUE_OUTSIDE", 140031);
+
+/**
+ * pRadar
+ */
+define("SEGMENT_HEIGHT_AUTO", 690001);
+define("RADAR_LAYOUT_STAR", 690011);
+define("RADAR_LAYOUT_CIRCLE", 690012);
+define("RADAR_LABELS_ROTATED", 690021);
+define("RADAR_LABELS_HORIZONTAL", 690022);
+
+/**
+ * pScatter
+ */
+define("SCATTER_MISSING_X_SERIE", 190001);
+define("SCATTER_MISSING_Y_SERIE", 190002);
+
+/**
+ * pSplit
+ */
+define("TEXT_POS_TOP", 690001);
+define("TEXT_POS_RIGHT", 690002);
+
+/**
+ * pSpring
+ */
+define("NODE_TYPE_FREE", 690001);
+define("NODE_TYPE_CENTRAL", 690002);
+define("NODE_SHAPE_CIRCLE", 690011);
+define("NODE_SHAPE_TRIANGLE", 690012);
+define("NODE_SHAPE_SQUARE", 690013);
+define("ALGORITHM_RANDOM", 690021);
+define("ALGORITHM_WEIGHTED", 690022);
+define("ALGORITHM_CIRCULAR", 690023);
+define("ALGORITHM_CENTRAL", 690024);
+define("LABEL_CLASSIC", 690031);
+define("LABEL_LIGHT", 690032);
+
+/**
+ * pStock
+ */
+define("STOCK_MISSING_SERIE", 180001);
+
+/**
+ * pSurface
+ */
+define("UNKNOWN", 0.123456789);
+define("IGNORED", -1);
+define("LABEL_POSITION_LEFT", 880001);
+define("LABEL_POSITION_RIGHT", 880002);
+define("LABEL_POSITION_TOP", 880003);
+define("LABEL_POSITION_BOTTOM", 880004);

+ 7 - 0
vendor/szymach/c-pchart/coverage.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+set -ev
+if [ ! -f bin/ocular.phar ]; then
+    wget -O bin/ocular.phar https://scrutinizer-ci.com/ocular.phar
+fi
+
+php bin/ocular.phar code-coverage:upload --format=php-clover tests/_output/coverage.xml

+ 9 - 0
vendor/szymach/c-pchart/docker-compose.yml

@@ -0,0 +1,9 @@
+---
+version: "3.4"
+
+services:
+    web:
+        image: fsiopen/docker-php-apache:alpine-8.1
+        container_name: c-pchart-php
+        volumes:
+            - ./:/var/www/application

+ 26 - 0
vendor/szymach/c-pchart/phpcs.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<ruleset name="Custom Standard">
+
+    <rule ref="PSR2"/>
+
+    <file>src</file>
+    <file>tests</file>
+
+    <exclude-pattern>vendor/*</exclude-pattern>
+    <exclude-pattern>*.db</exclude-pattern>
+    <exclude-pattern>*.color</exclude-pattern>
+    <exclude-pattern>*.ttf</exclude-pattern>
+
+    <arg name="cache" value=".phpcs-cache"/>
+    <arg name="encoding" value="utf-8"/>
+    <arg name="report" value="full"/>
+    <arg name="report-width" value="120"/>
+    <arg name="colors"/>
+    <arg value="p"/>
+
+    <rule ref="PSR2.Methods.MethodDeclaration">
+        <exclude-pattern>tests/unit/*Test.php</exclude-pattern>
+        <exclude-pattern>tests/_support/Helper/Unit.php</exclude-pattern>
+    </rule>
+
+</ruleset>

+ 107 - 0
vendor/szymach/c-pchart/resources/barcode/128B.db

@@ -0,0 +1,107 @@
+0;32;11011001100
+1;33;11001101100
+2;34;11001100110
+3;35;10010011000
+4;36;10010001100
+5;37;10001001100
+6;38;10011001000
+7;39;10011000100
+8;40;10001100100
+9;41;11001001000
+10;42;11001000100
+11;43;11000100100
+12;44;10110011100
+13;45;10011011100
+14;46;10011001110
+15;47;10111001100
+16;48;10011101100
+17;49;10011100110
+18;50;11001110010
+19;51;11001011100
+20;52;11001001110
+21;53;11011100100
+22;54;11001110100
+23;55;11101101110
+24;56;11101001100
+25;57;11100101100
+26;58;11100100110
+27;59;11101100100
+28;60;11100110100
+29;61;11100110010
+30;62;11011011000
+31;63;11011000110
+32;64;11000110110
+33;65;10100011000
+34;66;10001011000
+35;67;10001000110
+36;68;10110001000
+37;69;10001101000
+38;70;10001100010
+39;71;11010001000
+40;72;11000101000
+41;73;11000100010
+42;74;10110111000
+43;75;10110001110
+44;76;10001101110
+45;77;10111011000
+46;78;10111000110
+47;79;10001110110
+48;80;11101110110
+49;81;11010001110
+50;82;11000101110
+51;83;11011101000
+52;84;11011100010
+53;85;11011101110
+54;86;11101011000
+55;87;11101000110
+56;88;11100010110
+57;89;11101101000
+58;90;11101100010
+59;91;11100011010
+60;92;11101111010
+61;93;11001000010
+62;94;11110001010
+63;95;10100110000
+64;96;10100001100
+65;97;10010110000
+66;98;10010000110
+67;99;10000101100
+68;100;10000100110
+69;101;10110010000
+70;102;10110000100
+71;103;10011010000
+72;104;10011000010
+73;105;10000110100
+74;106;10000110010
+75;107;11000010010
+76;108;11001010000
+77;109;11110111010
+78;110;11000010100
+79;111;10001111010
+80;112;10100111100
+81;113;10010111100
+82;114;10010011110
+83;115;10111100100
+84;116;10011110100
+85;117;10011110010
+86;118;11110100100
+87;119;11110010100
+88;120;11110010010
+89;121;11011011110
+90;122;11011110110
+91;123;11110110110
+92;124;10101111000
+93;125;10100011110
+94;126;10001011110
+95;200;10111101000
+96;201;10111100010
+97;202;11110101000
+98;203;11110100010
+99;204;10111011110
+100;205;10111101110
+101;206;11101011110
+102;207;11110101110
+103;208;11010000100
+104;209;11010010000
+105;210;11010011100
+106;211;1100011101011

+ 44 - 0
vendor/szymach/c-pchart/resources/barcode/39.db

@@ -0,0 +1,44 @@
+0;101001101101
+1;110100101011
+2;101100101011
+3;110110010101
+4;101001101011
+5;110100110101
+6;101100110101
+7;101001011011
+8;110100101101
+9;101100101101
+A;110101001011
+B;101101001011
+C;110110100101
+D;101011001011
+E;110101100101
+F;101101100101
+G;101010011011
+H;110101001101
+I;101101001101
+J;101011001101
+K;110101010011
+L;101101010011
+M;110110101001
+N;101011010011
+O;110101101001
+P;101101101001
+Q;101010110011
+R;110101011001
+S;101101011001
+T;101011011001
+U;110010101011
+V;100110101011
+W;110011010101
+X;100101101011
+Y;110010110101
+Z;100110110101
+-;100101011011
+.;110010101101
+ ;100110101101
+$;100100100101
+/;100100101001
++;100101001001
+%;101001001001
+*;100101101101

+ 87 - 0
vendor/szymach/c-pchart/resources/doc/2d_pie.md

@@ -0,0 +1,87 @@
+# Drawing a 2D pie chart
+
+[Reference](http://wiki.pchart.net/doc.pie.draw2dpie.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Pie;
+use CpChart\Data;
+use CpChart\Image;
+
+// Create and populate data
+$data = new Data();
+$data->addPoints([40, 60, 15, 10, 6, 4], "ScoreA");
+$data->setSerieDescription("ScoreA", "Application A");
+
+// Define the absissa serie
+$data->addPoints(["<10", "10<>20", "20<>40", "40<>60", "60<>80", ">80"], "Labels");
+$data->setAbscissa("Labels");
+
+// Create the image
+$image = new Image(700, 230, $data);
+
+// Draw a solid background
+$backgroundSettings = [
+    "R"     => 173,
+    "G"     => 152,
+    "B"     => 217,
+    "Dash"  => 1,
+    "DashR" => 193,
+    "DashG" => 172,
+    "DashB" => 237
+];
+$image->drawFilledRectangle(0, 0, 700, 230, $backgroundSettings);
+
+//Draw a gradient overlay
+$gradientSettings = [
+    "StartR" => 209,
+    "StartG" => 150,
+    "StartB" => 231,
+    "EndR"   => 111,
+    "EndG"   => 3,
+    "EndB"   => 138,
+    "Alpha"  => 50
+];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $gradientSettings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR"   => 50,
+    "EndG"   => 50,
+    "EndB"   => 50,
+    "Alpha"  => 100
+]);
+
+// Add a border to the picture
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+// Write the picture title
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pPie - Draw 2D pie charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+// Set the default font properties
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 10, "R" => 80, "G" => 80, "B" => 80]);
+
+// Enable shadow computing
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 150, "G" => 150, "B" => 150, "Alpha" => 100]);
+$image->drawText(140, 200, "Single AA pass", ["R" => 0, "G" => 0, "B" => 0, "Align" => TEXT_ALIGN_TOPMIDDLE]);
+
+// Create and draw the chart
+$pieChart = new Pie($image, $data);
+$pieChart->draw2DPie(140, 125, ["SecondPass" => false]);
+$pieChart->draw2DPie(340, 125, ["DrawLabels" => true, "Border" => true]);
+$pieChart->draw2DPie(540, 125, [
+    "DataGapAngle"  => 10,
+    "DataGapRadius" => 6,
+    "Border" => true,
+    "BorderR" => 255,
+    "BorderG" => 255,
+    "BorderB" => 255
+]);
+$image->drawText(540, 200, "Extended AA pass / Splitted", ["R" => 0, "G" => 0, "B" => 0, "Align" => TEXT_ALIGN_TOPMIDDLE]);
+
+$pieChart->pChartObject->autoOutput("example.draw2DPie.png");
+```
+

+ 72 - 0
vendor/szymach/c-pchart/resources/doc/2d_ring.md

@@ -0,0 +1,72 @@
+# Drawing a 2D ring chart
+
+[Reference](http://wiki.pchart.net/doc.pie.draw2dring.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Pie;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([50, 2, 3, 4, 7, 10, 25, 48, 41, 10], "ScoreA");
+$data->setSerieDescription("ScoreA", "Application A");
+
+/* Define the absissa serie */
+$data->addPoints(["A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7", "I8", "J9"], "Labels");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(300, 260, $data);
+
+/* Draw a solid background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 300, 300, $settings);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 300, 260, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 300, 20, DIRECTION_VERTICAL, ["StartR" => 0, "StartG" => 0,
+    "StartB" => 0, "EndR" => 50, "EndG" => 50, "EndB" => 50, "Alpha" => 100]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 299, 259, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pPie - Draw 2D ring charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Set the default font properties */
+$image->setFontProperties([
+    "FontName" => "Forgotte.ttf",
+    "FontSize" => 10,
+    "R" => 80,
+    "G" => 80,
+    "B" => 80
+]);
+
+/* Enable shadow computing */
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 50]);
+
+/* Create the pPie object */
+$pieChart = new Pie($image, $data);
+
+/* Draw an AA pie chart */
+$pieChart->draw2DRing(160, 140, ["DrawLabels" => true, "LabelStacked" => true, "Border" => true]);
+
+/* Write the legend box */
+$image->setShadow(false);
+$pieChart->drawPieLegend(15, 40, ["Alpha" => 20]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.draw2DRing.png");
+```

+ 119 - 0
vendor/szymach/c-pchart/resources/doc/3d_pie.md

@@ -0,0 +1,119 @@
+# Drawing a 3D pie chart
+
+[Reference](http://wiki.pchart.net/doc.pie.draw3dpie.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Pie;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([40, 30, 20], "ScoreA");
+$data->setSerieDescription("ScoreA", "Application A");
+
+/* Define the absissa serie */
+$data->addPoints(["A", "B", "C"], "Labels");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data, true);
+
+/* Draw a solid background */
+$image->drawFilledRectangle(0, 0, 700, 230, [
+    "R" => 173,
+    "G" => 152,
+    "B" => 217,
+    "Dash" => 1,
+    "DashR" => 193,
+    "DashG" => 172,
+    "DashB" => 237
+]);
+
+/* Draw a gradient overlay */
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, [
+    "StartR" => 209,
+    "StartG" => 150,
+    "StartB" => 231,
+    "EndR" => 111,
+    "EndG" => 3,
+    "EndB" => 138,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pPie - Draw 3D pie charts", ["R" => 255, "G" => 255,
+    "B" => 255]);
+
+/* Set the default font properties */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 10,
+    "R" => 80, "G" => 80, "B" => 80]);
+
+/* Create the pPie object */
+$pieChart = new Pie($image, $data);
+
+/* Define the slice color */
+$pieChart->setSliceColor(0, ["R" => 143, "G" => 197, "B" => 0]);
+$pieChart->setSliceColor(1, ["R" => 97, "G" => 77, "B" => 63]);
+$pieChart->setSliceColor(2, ["R" => 97, "G" => 113, "B" => 63]);
+
+/* Draw a simple pie chart */
+$pieChart->draw3DPie(120, 125, ["SecondPass" => false]);
+
+/* Draw an AA pie chart */
+$pieChart->draw3DPie(340, 125, ["DrawLabels" => true, "Border" => true]);
+
+/* Enable shadow computing */
+$image->setShadow(true, ["X" => 3, "Y" => 3, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw a splitted pie chart */
+$pieChart->draw3DPie(560, 125, ["WriteValues" => true, "DataGapAngle" => 10, "DataGapRadius" => 6, "Border" => true]);
+
+/* Write the legend */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]);
+$image->drawText(120, 200, "Single AA pass", [
+    "DrawBox" => true,
+    "BoxRounded" => true,
+    "R" => 0,
+    "G" => 0,
+    "B" => 0,
+    "Align" => TEXT_ALIGN_TOPMIDDLE
+]);
+$image->drawText(440, 200, "Extended AA pass / Splitted", [
+    "DrawBox" => true,
+    "BoxRounded" => true,
+    "R" => 0,
+    "G" => 0,
+    "B" => 0,
+    "Align" => TEXT_ALIGN_TOPMIDDLE
+]);
+
+/* Write the legend box */
+$image->setFontProperties([
+    "FontName" => "Silkscreen.ttf",
+    "FontSize" => 6,
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+$pieChart->drawPieLegend(600, 8, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.draw3DPie.png");
+```

+ 78 - 0
vendor/szymach/c-pchart/resources/doc/3d_ring.md

@@ -0,0 +1,78 @@
+# Drawing a 3D ring chart
+
+[Reference](http://wiki.pchart.net/doc.pie.draw3dring.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Pie;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([50, 2, 3, 4, 7, 10, 25, 48, 41, 10], "ScoreA");
+$data->setSerieDescription("ScoreA", "Application A");
+
+/* Define the absissa serie */
+$data->addPoints(["A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7", "I8", "J9"], "Labels");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw a solid background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pPie - Draw 3D ring charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Set the default font properties */
+$image->setFontProperties([
+    "FontName" => "Forgotte.ttf",
+    "FontSize" => 10,
+    "R" => 80,
+    "G" => 80,
+    "B" => 80
+]);
+
+/* Enable shadow computing */
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 50]);
+
+/* Create the pPie object */
+$pieChart = new Pie($image, $data);
+
+/* Draw an AA pie chart */
+$pieChart->draw3DRing(200, 200, ["DrawLabels" => true, "LabelStacked" => true, "Border" => true]);
+
+/* Write the legend box */
+$pieChart->drawPieLegend(80, 360, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER, "Alpha" => 20]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.draw3DRing.png");
+```

+ 72 - 0
vendor/szymach/c-pchart/resources/doc/area.md

@@ -0,0 +1,72 @@
+# Drawing an area chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawareachart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+$data = new Data();
+for ($i = 0; $i <= 30; $i++) {
+    $data->addPoints(rand(1, 15), "Probe 1");
+}
+$data->setSerieTicks("Probe 2", 4);
+$data->setAxisName(0, "Temperatures");
+
+// Create the Image object
+$image = new Image(700, 230, $data);
+
+/* Turn off Antialiasing */
+$image->Antialias = false;
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(150, 35, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Define the chart area */
+$image->setGraphArea(60, 40, 650, 200);
+
+/* Draw the scale */
+$scaleSettings = [
+    "XMargin" => 10,
+    "YMargin" => 10,
+    "Floating" => true,
+    "GridR" => 200,
+    "GridG" => 200,
+    "GridB" => 200,
+    "DrawSubTicks" => true,
+    "CycleBackground" => true
+];
+$image->drawScale($scaleSettings);
+
+/* Write the chart legend */
+$image->drawLegend(600, 20, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Turn on Antialiasing */
+$image->Antialias = true;
+
+/* Enable shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the area chart */
+$Threshold = [];
+$Threshold[] = ["Min" => 0, "Max" => 5, "R" => 207, "G" => 240, "B" => 20, "Alpha" => 70];
+$Threshold[] = ["Min" => 5, "Max" => 10, "R" => 240, "G" => 232, "B" => 20, "Alpha" => 70];
+$Threshold[] = ["Min" => 10, "Max" => 20, "R" => 240, "G" => 191, "B" => 20, "Alpha" => 70];
+$image->drawAreaChart(["Threshold" => $Threshold]);
+
+/* Write the thresholds */
+$image->drawThreshold(5, ["WriteCaption" => true, "Caption" => "Warn Zone", "Alpha" => 70, "Ticks" => 2, "R" => 0, "G" => 0, "B" => 255]);
+$image->drawThreshold(10, ["WriteCaption" => true, "Caption" => "Error Zone", "Alpha" => 70, "Ticks" => 2, "R" => 0, "G" => 0, "B" => 255]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawAreaChart.threshold.png");
+```

+ 64 - 0
vendor/szymach/c-pchart/resources/doc/bar.md

@@ -0,0 +1,64 @@
+# Drawing a bar chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawbarchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([13251, 4118, 3087, 1460, 1248, 156, 26, 9, 8], "Hits");
+$data->setAxisName(0, "Hits");
+$data->addPoints(["Firefox", "Chrome", "Internet Explorer", "Opera", "Safari", "Mozilla", "SeaMonkey", "Camino", "Lunascape"], "Browsers");
+$data->setSerieDescription("Browsers", "Browsers");
+$data->setAbscissa("Browsers");
+
+/* Create the Image object */
+$image = new Image(500, 500, $data);
+$image->drawGradientArea(0, 0, 500, 500, DIRECTION_VERTICAL, [
+    "StartR" => 240,
+    "StartG" => 240,
+    "StartB" => 240,
+    "EndR" => 180,
+    "EndG" => 180,
+    "EndB" => 180,
+    "Alpha" => 100
+]);
+$image->drawGradientArea(0, 0, 500, 500, DIRECTION_HORIZONTAL, [
+    "StartR" => 240,
+    "StartG" => 240,
+    "StartB" => 240,
+    "EndR" => 180,
+    "EndG" => 180,
+    "EndB" => 180,
+    "Alpha" => 20
+]);
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Draw the chart scale */
+$image->setGraphArea(100, 30, 480, 480);
+$image->drawScale([
+    "CycleBackground" => true,
+    "DrawSubTicks" => true,
+    "GridR" => 0,
+    "GridG" => 0,
+    "GridB" => 0,
+    "GridAlpha" => 10,
+    "Pos" => SCALE_POS_TOPBOTTOM
+]);
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the chart */
+$image->drawBarChart(["DisplayPos" => LABEL_POS_INSIDE, "DisplayValues" => true, "Rounded" => true, "Surrounding" => 30]);
+
+/* Write the legend */
+$image->drawLegend(570, 215, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawBarChart.vertical.png");
+```

+ 89 - 0
vendor/szymach/c-pchart/resources/doc/barcode_128.md

@@ -0,0 +1,89 @@
+# Drawing a barcode 128
+
+[Reference](http://wiki.pchart.net/doc.barcode128.pBarcode128.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Barcode\Barcode128;
+use CpChart\Image;
+
+/* Create the Image object */
+$image = new Image(700, 230);
+
+/* Draw the background */
+$image->drawFilledRectangle(0, 0, 700, 230, [
+    "R" => 170,
+    "G" => 183,
+    "B" => 87,
+    "Dash" => 1,
+    "DashR" => 190,
+    "DashG" => 203,
+    "DashB" => 107
+]);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Draw the top bar */
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "Barcode 128 - Add barcode to your pictures", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Create the barcode 128 object */
+$barcodeChart = new Barcode128();
+
+/* Draw a simple barcode */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$barcodeChart->draw($image, "pChart Rocks!", 50, 50, ["ShowLegend" => true, "DrawArea" => true]);
+
+/* Draw a rotated barcode */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 12]);
+$barcodeChart->draw($image, "Turn me on", 650, 50, ["ShowLegend" => true, "DrawArea" => true, "Angle" => 90]);
+
+/* Draw a rotated barcode */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 12]);
+$barcodeChart->draw($image, "Do what you want !", 290, 140, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "AreaR" => 150,
+    "AreaG" => 30,
+    "AreaB" => 27,
+    "ShowLegend" => true,
+    "DrawArea" => true,
+    "Angle" => 350,
+    "AreaBorderR" => 70,
+    "AreaBorderG" => 20,
+    "AreaBorderB" => 20
+]);
+
+/* Render the picture */
+$image->autoOutput("example.barcode128.png");
+```

+ 82 - 0
vendor/szymach/c-pchart/resources/doc/barcode_39.md

@@ -0,0 +1,82 @@
+# Drawing a barcode 39
+
+[Reference](http://wiki.pchart.net/doc.barcode39.pBarcode39.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Barcode\Barcode39;
+use CpChart\Image;
+
+/* Create the Image object */
+$image = new Image(700, 230);
+
+/* Draw the background */
+$image->drawFilledRectangle(0, 0, 700, 230, [
+    "R" => 170,
+    "G" => 183,
+    "B" => 87,
+    "Dash" => 1,
+    "DashR" => 190,
+    "DashG" => 203,
+    "DashB" => 107
+]);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Draw the picture border */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "Barcode 39 - Add barcode to your pictures", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Create the barcode 39 object */
+$barcodeChart = new Barcode39();
+
+/* Draw a simple barcode */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$barcodeChart->draw($image, "pChart Rocks!", 50, 50, ["ShowLegend" => true, "DrawArea" => true]);
+
+/* Draw a rotated barcode */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 12]);
+$barcodeChart->draw($image, "Turn me on", 650, 50, ["ShowLegend" => true, "DrawArea" => true, "Angle" => 90]);
+
+/* Draw a rotated barcode */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 12]);
+$barcodeChart->draw($image, "Do what you want !", 290, 140, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "AreaR" => 150,
+    "AreaG" => 30,
+    "AreaB" => 27,
+    "ShowLegend" => true,
+    "DrawArea" => true,
+    "Angle" => 350,
+    "AreaBorderR" => 70,
+    "AreaBorderG" => 20,
+    "AreaBorderB" => 20
+]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.barcode39.png");
+```

+ 70 - 0
vendor/szymach/c-pchart/resources/doc/best_fit.md

@@ -0,0 +1,70 @@
+# Drawing a best fit chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawbestfit.html)
+
+``` php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+for ($i = 0; $i <= 20; $i++) {
+    $data->addPoints(rand(10, 30) + $i, "Probe 1");
+}
+for ($i = 0; $i <= 20; $i++) {
+    $data->addPoints(rand(0, 10) + $i, "Probe 2");
+}
+$data->setAxisName(0, "Temperatures");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Turn off Antialiasing */
+$image->Antialias = false;
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => ".Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(150, 35, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => ".pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Define the chart area */
+$image->setGraphArea(60, 40, 650, 200);
+
+/* Draw the scale */
+$scaleSettings = [
+    "XMargin" => 10,
+    "YMargin" => 10,
+    "Floating" => true,
+    "GridR" => 200,
+    "GridG" => 200,
+    "GridB" => 200,
+    "DrawSubTicks" => true,
+    "CycleBackground" => true
+];
+$image->drawScale($scaleSettings);
+
+/* Turn on Antialiasing */
+$image->Antialias = true;
+
+/* Draw the line of best fit */
+$image->drawBestFit();
+
+/* Turn on shadows */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the line chart */
+$image->drawPlotChart();
+
+/* Write the chart legend */
+$image->drawLegend(580, 20, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawBestFit.png");
+```

+ 107 - 0
vendor/szymach/c-pchart/resources/doc/bubble.md

@@ -0,0 +1,107 @@
+# Drawing a linear bubble chart
+
+[Reference](http://wiki.pchart.net/doc.bubble.drawbubblechart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Bubble;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([34, 55, 15, 62, 38, 42], "Probe1");
+$data->addPoints([5, 10, 8, 9, 15, 10], "Probe1Weight");
+$data->addPoints([5, 10, -5, -1, 0, -10], "Probe2");
+$data->addPoints([6, 10, 14, 10, 14, 6], "Probe2Weight");
+$data->setSerieDescription("Probe1", "This year");
+$data->setSerieDescription("Probe2", "Last year");
+$data->setAxisName(0, "Current stock");
+$data->addPoints(["Apple", "Banana", "Orange", "Lemon", "Peach", "Strawberry"], "Product");
+$data->setAbscissa("Product");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawBubbleChart() - draw a linear bubble chart", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Write the title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(40, 55, "Current Stock / Needs chart", ["FontSize" => 14, "Align" => TEXT_ALIGN_BOTTOMLEFT]);
+
+/* Change the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Create the Bubble chart object and scale up */
+$bubbleChart = new Bubble($image, $data);
+
+/* Scale up for the bubble chart */
+$bubbleDataSeries = ["Probe1", "Probe2"];
+$bubbleWeightSeries = ["Probe1Weight", "Probe2Weight"];
+$bubbleChart->bubbleScale($bubbleDataSeries, $bubbleWeightSeries);
+
+/* Draw the 1st chart */
+$image->setGraphArea(40, 60, 430, 190);
+$image->drawFilledRectangle(40, 60, 430, 190, ["R" => 255, "G" => 255, "B" => 255,
+    "Surrounding" => -200, "Alpha" => 10]);
+$image->drawScale(["DrawSubTicks" => true, "CycleBackground" => true]);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 30]);
+$bubbleChart->drawBubbleChart($bubbleDataSeries, $bubbleWeightSeries);
+
+/* Draw the 2nd scale */
+$image->setShadow(false);
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "DrawSubTicks" => true]);
+
+/* Draw the 2nd stock chart */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 30]);
+$bubbleChart->drawbubbleChart($bubbleDataSeries, $bubbleWeightSeries);
+
+/* Write the chart legend */
+$image->drawLegend(550, 215, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawBubbleChart.png");
+```

+ 83 - 0
vendor/szymach/c-pchart/resources/doc/cache.md

@@ -0,0 +1,83 @@
+# Cache operations
+
+[Reference](http://wiki.pchart.net/doc.pcache.pcache.html)
+
+To speed up the process of creating charts, you can store them in the cache files
+using the `CpChart\Cache` class. It will create two files - `cache.db` and
+`index.db` in a dedicated directory (`app\cache` by default, relative to the library's
+root directory), but you can change these using the `$settings` array passed
+to the object's constructor.
+
+Should you decide to use the cache component, the following sections describe
+how you can do that.
+
+## Using cache to store and retrieve chart data
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Cache;
+use CpChart\Data;
+use CpChart\Image;
+
+// Standard chart creation
+$data = new Data();
+$data->addPoints([1, 3, 4, 3, 5]);
+
+$image = new Image(700, 230, $data);
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->setGraphArea(60, 40, 670, 190);
+$image->drawScale();
+$image->drawSplineChart();
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "Test of the pCache class", ["R" => 255, "G" => 255, "B" => 255]);
+
+// Create a cache object and store the chart in it
+$cache = new Cache([
+    // Optionally change the default directory and file names
+    'CacheFolder' => 'path/to/your/cache/directory',
+    'CacheIndex' => 'name_of_the_index_file.db',
+    'CacheDB' => 'name_of_the_database_file.db'
+]);
+$chartHash = $cache->getHash($data); // Chart dentifier in the cache
+$cache->writeToCache($chartHash, $image);
+
+// Create an image file from cache
+$cache->saveFromCache($chartHash, "example.drawCachedSpline.png");
+
+// Directly stroke the saved data to the browser
+$cache->strokeFromCache($chartHash)
+
+// Automatically choose a way to output stored data
+$cache->autoOutput($chartHash)
+```
+
+## Removal operations
+
+```php
+// Assuming we have $chartHash and $cache variables from the previous example
+
+// This will remove the chart by it's hash
+$cache->remove($chartHash);
+
+// This will remove every chart in cache older than the amount of seconds passed
+// into the argument's parameter
+$cache->removeOlderThan(60 * 60 * 24); // Remove data older than 24 hours
+
+// This flushes the cache completely and regenerates the .db files
+$cache->flush();
+```
+
+There is also the function called `CpChart\Cache::dbRemoval(array $settings)`,
+but it only covers two use cases - removing by chart hash and age. Since there
+are dedicated methods for each of them (`remove` and `removeOlderThan`, respectively),
+there is no reason to cover it any further.

+ 92 - 0
vendor/szymach/c-pchart/resources/doc/contour.md

@@ -0,0 +1,92 @@
+# Drawing a contour chart
+
+[Reference](http://wiki.pchart.net/doc.surface.drawcontour.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Surface;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Image object */
+$image = new Image(400, 400);
+
+/* Create a solid background */
+$image->drawFilledRectangle(0, 0, 400, 400, [
+    "R" => 179,
+    "G" => 217,
+    "B" => 91,
+    "Dash" => 1,
+    "DashR" => 199,
+    "DashG" => 237,
+    "DashB" => 111
+]);
+
+/* Do a gradient overlay */
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 194,
+    "StartG" => 231,
+    "StartB" => 44,
+    "EndR" => 43,
+    "EndG" => 107,
+    "EndB" => 58,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pSurface() :: 2D surface charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Define the charting area */
+$image->setGraphArea(20, 40, 380, 380);
+$image->drawFilledRectangle(20, 40, 380, 380, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 20
+]);
+
+$image->setShadow(true, ["X" => 1, "Y" => 1]);
+
+/* Create the surface object */
+$surfaceChart = new Surface($image);
+
+/* Set the grid size */
+$surfaceChart->setGrid(20, 20);
+
+/* Write the axis labels */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$surfaceChart->writeXLabels(["Position" => LABEL_POSITION_BOTTOM]);
+$surfaceChart->writeYLabels();
+
+/* Add random values */
+for ($i = 0; $i <= 50; $i++) {
+    $surfaceChart->addPoint(rand(0, 20), rand(0, 20), rand(0, 100));
+}
+
+/* Compute the missing points */
+$surfaceChart->computeMissing();
+
+/* Draw the surface chart */
+$surfaceChart->drawSurface(["Border" => true, "Surrounding" => 40]);
+
+/* Draw the contour with a threshold of 50 */
+$surfaceChart->drawContour(50, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.surface.png");
+```

+ 125 - 0
vendor/szymach/c-pchart/resources/doc/filled_spline.md

@@ -0,0 +1,125 @@
+# Drawing a filled spline chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawFilledSplineChart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->setAxisName(0, "Strength");
+for ($i = 0; $i <= 720; $i = $i + 20) {
+    $data->addPoints(cos(deg2rad($i)) * 100, "Probe 1");
+    $data->addPoints(cos(deg2rad($i + 90)) * 60, "Probe 2");
+}
+
+/* Create the Image object */
+$image = new Image(847, 304, $data);
+$image->drawGradientArea(0, 0, 847, 304, DIRECTION_VERTICAL, [
+    "StartR" => 47,
+    "StartG" => 47,
+    "StartB" => 47,
+    "EndR" => 17,
+    "EndG" => 17,
+    "EndB" => 17,
+    "Alpha" => 100
+]);
+$image->drawGradientArea(0, 250, 847, 304, DIRECTION_VERTICAL, [
+    "StartR" => 47,
+    "StartG" => 47,
+    "StartB" => 47,
+    "EndR" => 27,
+    "EndG" => 27,
+    "EndB" => 27,
+    "Alpha" => 100
+]);
+$image->drawLine(0, 249, 847, 249, ["R" => 0, "G" => 0, "B" => 0]);
+$image->drawLine(0, 250, 847, 250, ["R" => 70, "G" => 70, "B" => 70]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 846, 303, ["R" => 204, "G" => 204, "B" => 204]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawText(423, 14, "Cyclic magnetic field strength", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+]);
+
+/* Define the chart area */
+$image->setGraphArea(58, 27, 816, 228);
+
+/* Draw a rectangle */
+$image->drawFilledRectangle(58, 27, 816, 228, [
+    "R" => 0,
+    "G" => 0,
+    "B" => 0,
+    "Dash" => true,
+    "DashR" => 0,
+    "DashG" => 51,
+    "DashB" => 51,
+    "BorderR" => 0,
+    "BorderG" => 0,
+    "BorderB" => 0
+]);
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]);
+
+/* Draw the scale */
+$image->setFontProperties(["R" => 255, "G" => 255, "B" => 255]);
+$ScaleSettings = [
+    "XMargin" => 4,
+    "DrawSubTicks" => true,
+    "GridR" => 255,
+    "GridG" => 255,
+    "GridB" => 255,
+    "AxisR" => 255,
+    "AxisG" => 255,
+    "AxisB" => 255,
+    "GridAlpha" => 30,
+    "CycleBackground" => true
+];
+$image->drawScale($ScaleSettings);
+
+/* Draw the spline chart */
+$image->drawFilledSplineChart();
+
+/* Write the chart boundaries */
+$BoundsSettings = [
+    "MaxDisplayR" => 237,
+    "MaxDisplayG" => 23,
+    "MaxDisplayB" => 48,
+    "MinDisplayR" => 23,
+    "MinDisplayG" => 144,
+    "MinDisplayB" => 237
+];
+$image->writeBounds(BOUND_BOTH, $BoundsSettings);
+
+/* Write the 0 line */
+$image->drawThreshold(0, ["WriteCaption" => true]);
+
+/* Write the chart legend */
+$image->setFontProperties(["R" => 255, "G" => 255, "B" => 255]);
+$image->drawLegend(560, 266, ["Style" => LEGEND_NOBORDER]);
+
+/* Write the 1st data series statistics */
+$settings = ["R" => 188, "G" => 224, "B" => 46, "Align" => TEXT_ALIGN_BOTTOMLEFT];
+$image->drawText(620, 270, "Max : " . ceil($data->getMax("Probe 1")), $settings);
+$image->drawText(680, 270, "Min : " . ceil($data->getMin("Probe 1")), $settings);
+$image->drawText(740, 270, "Avg : " . ceil($data->getSerieAverage("Probe 1")), $settings);
+
+/* Write the 2nd data series statistics */
+$settings = ["R" => 224, "G" => 100, "B" => 46, "Align" => TEXT_ALIGN_BOTTOMLEFT];
+$image->drawText(620, 283, "Max : " . ceil($data->getMax("Probe 2")), $settings);
+$image->drawText(680, 283, "Min : " . ceil($data->getMin("Probe 2")), $settings);
+$image->drawText(740, 283, "Avg : " . ceil($data->getSerieAverage("Probe 2")), $settings);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawFilledSplineChart.png");
+```

+ 75 - 0
vendor/szymach/c-pchart/resources/doc/filled_step.md

@@ -0,0 +1,75 @@
+# Drawing a filled step chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawfilledstepchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([-4, 2, VOID, 12, 8, 3], "Probe 1");
+$data->addPoints([3, 12, 15, 8, 5, -5], "Probe 2");
+$data->addPoints([2, 7, 5, 18, 19, 22], "Probe 3");
+$data->setSerieTicks("Probe 2", 4);
+$data->setAxisName(0, "Temperatures");
+$data->addPoints(["Jan", "Feb", "Mar", "Apr", "May", "Jun"], "Labels");
+$data->setSerieDescription("Labels", "Months");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = ["StartR" => 219, "StartG" => 231, "StartB" => 139, "EndR" => 1, "EndG" => 138, "EndB" => 68, "Alpha" => 50];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawFilledStepChart() - draw a filled step chart", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(250, 55, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Draw the scale and the 1st chart */
+$image->setGraphArea(60, 60, 450, 190);
+$image->drawFilledRectangle(60, 60, 450, 190, ["R" => 255, "G" => 255, "B" => 255, "Surrounding" => -200, "Alpha" => 10]);
+$image->drawScale(["DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawFilledStepChart(["ForceTransparency" => 40, "DisplayValues" => true, "DisplayColor" => DISPLAY_AUTO]);
+$image->setShadow(false);
+
+/* Draw the scale and the 2nd chart */
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, ["R" => 255, "G" => 255, "B" => 255, "Surrounding" => -200, "Alpha" => 10]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => -1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->drawFilledStepChart(["ForceTransparency" => 40]);
+$image->setShadow(false);
+
+/* Write the chart legend */
+$image->drawLegend(510, 205, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawFilledStepChart.png");
+```

+ 50 - 0
vendor/szymach/c-pchart/resources/doc/line.md

@@ -0,0 +1,50 @@
+# Drawing a line chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawlinechart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Build a dataset */
+$data = new Data();
+$data->addPoints([-4, VOID, VOID, 12, 8, 3], "Probe 1");
+$data->addPoints([3, 12, 15, 8, 5, -5], "Probe 2");
+$data->addPoints([2, 7, 5, 18, 19, 22], "Probe 3");
+$data->setSerieTicks("Probe 2", 4);
+$data->setSerieWeight("Probe 3", 2);
+$data->setAxisName(0, "Temperatures");
+$data->addPoints(["Jan", "Feb", "Mar", "Apr", "May", "Jun"], "Labels");
+$data->setSerieDescription("Labels", "Months");
+$data->setAbscissa("Labels");
+
+/* Create the 1st chart */
+$image = new Image(700, 230, $data);
+$image->setGraphArea(60, 60, 450, 190);
+$image->drawFilledRectangle(60, 60, 450, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->setFontProperties(["FontName" => "fonts/pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawLineChart(["DisplayValues" => true, "DisplayColor" => DISPLAY_AUTO]);
+$image->setShadow(false);
+
+/* Create the 2nd chart */
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, ["R" => 255, "G" => 255, "B" => 255, "Surrounding" => -200, "Alpha" => 10]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => -1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->drawLineChart();
+$image->setShadow(false);
+
+/* Write the legend */
+$image->drawLegend(510, 205, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+$image->autoOutput("example.drawLineChart.png");
+```

+ 67 - 0
vendor/szymach/c-pchart/resources/doc/plot.md

@@ -0,0 +1,67 @@
+# Drawing a plot chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawplotchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+for ($i = 0; $i <= 20; $i++) {
+    $data->addPoints(rand(0, 20), "Probe 1");
+}
+for ($i = 0; $i <= 20; $i++) {
+    $data->addPoints(rand(0, 20), "Probe 2");
+}
+$data->setSerieShape("Probe 1", SERIE_SHAPE_FILLEDTRIANGLE);
+$data->setSerieShape("Probe 2", SERIE_SHAPE_FILLEDSQUARE);
+$data->setAxisName(0, "Temperatures");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Turn off Antialiasing */
+$image->Antialias = false;
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(150, 35, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Define the chart area */
+$image->setGraphArea(60, 40, 650, 200);
+
+/* Draw the scale */
+$scaleSettings = [
+    "XMargin" => 10,
+    "YMargin" => 10,
+    "Floating" => true,
+    "GridR" => 200,
+    "GridG" => 200,
+    "GridB" => 200,
+    "DrawSubTicks" => true,
+    "CycleBackground" => true
+];
+$image->drawScale($scaleSettings);
+
+/* Turn on Antialiasing */
+$image->Antialias = true;
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the line chart */
+$image->drawPlotChart();
+
+/* Write the chart legend */
+$image->drawLegend(580, 20, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawPlotChart.simple.png");
+```

+ 96 - 0
vendor/szymach/c-pchart/resources/doc/polar.md

@@ -0,0 +1,96 @@
+# Drawing a polar chart
+
+[Reference](http://wiki.pchart.net/doc.draw.polar.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Radar;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([10, 20, 30, 40, 50, 60, 70, 80, 90], "ScoreA");
+$data->addPoints([20, 40, 50, 12, 10, 30, 40, 50, 60], "ScoreB");
+$data->setSerieDescription("ScoreA", "Coverage A");
+$data->setSerieDescription("ScoreB", "Coverage B");
+
+/* Define the absissa serie */
+$data->addPoints([40, 80, 120, 160, 200, 240, 280, 320, 360], "Coord");
+$data->setAbscissa("Coord");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw a solid background */
+$settings = ["R" => 179, "G" => 217, "B" => 91, "Dash" => 1, "DashR" => 199, "DashG" => 237, "DashB" => 111];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay some gradient areas */
+$settings = ["StartR" => 194, "StartG" => 231, "StartB" => 44, "EndR" => 43, "EndG" => 107, "EndB" => 58, "Alpha" => 50];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pRadar - Draw polar charts", ["R" => 255, "G" => 255,
+    "B" => 255]);
+
+/* Set the default font properties */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 10,
+    "R" => 80, "G" => 80, "B" => 80]);
+
+/* Enable shadow computing */
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0,
+    "Alpha" => 10]);
+
+/* Create the pRadar object */
+$radarChart = new Radar();
+
+/* Draw a polar chart */
+$image->setGraphArea(10, 25, 340, 225);
+$options = ["BackgroundGradient" => [
+    "StartR" => 255,
+    "StartG" => 255,
+    "StartB" => 255,
+    "StartAlpha" => 100,
+    "EndR" => 207,
+    "EndG" => 227,
+    "EndB" => 125,
+    "EndAlpha" => 50
+]];
+$radarChart->drawPolar($image, $data, $options);
+
+/* Draw a polar chart */
+$image->setGraphArea(350, 25, 690, 225);
+$options = [
+    "LabelPos" => RADAR_LABELS_HORIZONTAL,
+    "BackgroundGradient" => [
+        "StartR" => 255, "StartG" => 255, "StartB" => 255, "StartAlpha" => 50, "EndR" => 32,
+        "EndG" => 109, "EndB" => 174, "EndAlpha" => 30
+    ],
+    "AxisRotation" => 0,
+    "DrawPoly" => true,
+    "PolyAlpha" => 50
+];
+$radarChart->drawPolar($image, $data, $options);
+
+/* Write the chart legend */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawLegend(270, 205, ["Style" => LEGEND_BOX, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.polar.png");
+```

+ 71 - 0
vendor/szymach/c-pchart/resources/doc/progress.md

@@ -0,0 +1,71 @@
+# Drawing a progress chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawprogress.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+$image = new Image(700, 250);
+
+/* Enable shadow support */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]);
+
+/* Left Red bar */
+$progressOptions = ["R" => 209, "G" => 31, "B" => 27, "Surrounding" => 20, "BoxBorderR" => 0,
+    "BoxBorderG" => 0, "BoxBorderB" => 0, "BoxBackR" => 255, "BoxBackG" => 255, "BoxBackB" => 255,
+    "RFade" => 206, "GFade" => 133, "BFade" => 30, "ShowLabel" => true];
+$image->drawProgress(40, 60, 77, $progressOptions);
+
+/* Left Orange bar */
+$progressOptions = ["Width" => 165, "R" => 209, "G" => 125, "B" => 27, "Surrounding" => 20,
+    "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0, "BoxBackR" => 255, "BoxBackG" => 255,
+    "BoxBackB" => 255, "NoAngle" => true, "ShowLabel" => true, "LabelPos" => LABEL_POS_RIGHT];
+$image->drawProgress(40, 100, 50, $progressOptions);
+
+/* Left Yellow bar */
+$progressOptions = ["Width" => 165, "R" => 209, "G" => 198, "B" => 27, "Surrounding" => 20,
+    "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0, "BoxBackR" => 255, "BoxBackG" => 255,
+    "BoxBackB" => 255, "ShowLabel" => true, "LabelPos" => LABEL_POS_LEFT];
+$image->drawProgress(75, 140, 25, $progressOptions);
+
+/* Left Green bar */
+$progressOptions = ["Width" => 400, "R" => 134, "G" => 209, "B" => 27, "Surrounding" => 20,
+    "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0, "BoxBackR" => 255, "BoxBackG" => 255,
+    "BoxBackB" => 255, "RFade" => 206, "GFade" => 133, "BFade" => 30, "ShowLabel" => true,
+    "LabelPos" => LABEL_POS_CENTER];
+$image->drawProgress(40, 180, 80, $progressOptions);
+
+/* Right vertical Red bar */
+$progressOptions = ["Width" => 20, "Height" => 150, "R" => 209, "G" => 31, "B" => 27,
+    "Surrounding" => 20, "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0,
+    "BoxBackR" => 255, "BoxBackG" => 255, "BoxBackB" => 255, "RFade" => 206, "GFade" => 133,
+    "BFade" => 30, "ShowLabel" => true, "Orientation" => ORIENTATION_VERTICAL, "LabelPos" => LABEL_POS_BOTTOM];
+$image->drawProgress(500, 200, 77, $progressOptions);
+
+/* Right vertical Orange bar */
+$progressOptions = ["Width" => 20, "Height" => 150, "R" => 209, "G" => 125,
+    "B" => 27, "Surrounding" => 20, "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0,
+    "BoxBackR" => 255, "BoxBackG" => 255, "BoxBackB" => 255, "NoAngle" => true, "ShowLabel" => true,
+    "Orientation" => ORIENTATION_VERTICAL, "LabelPos" => LABEL_POS_TOP];
+$image->drawProgress(540, 200, 50, $progressOptions);
+
+/* Right vertical Yellow bar */
+$progressOptions = ["Width" => 20, "Height" => 150, "R" => 209, "G" => 198,
+    "B" => 27, "Surrounding" => 20, "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0,
+    "BoxBackR" => 255, "BoxBackG" => 255, "BoxBackB" => 255, "ShowLabel" => true,
+    "Orientation" => ORIENTATION_VERTICAL, "LabelPos" => LABEL_POS_INSIDE];
+$image->drawProgress(580, 200, 25, $progressOptions);
+
+/* Right vertical Green bar */
+$progressOptions = ["Width" => 20, "Height" => 150, "R" => 134, "G" => 209,
+    "B" => 27, "Surrounding" => 20, "BoxBorderR" => 0, "BoxBorderG" => 0, "BoxBorderB" => 0,
+    "BoxBackR" => 255, "BoxBackG" => 255, "BoxBackB" => 255, "RFade" => 206, "GFade" => 133,
+    "BFade" => 30, "ShowLabel" => true, "Orientation" => ORIENTATION_VERTICAL, "LabelPos" => LABEL_POS_CENTER];
+$image->drawProgress(620, 200, 80, $progressOptions);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawProgressChart.png");
+```

+ 98 - 0
vendor/szymach/c-pchart/resources/doc/radar.md

@@ -0,0 +1,98 @@
+# Drawing a radar chart
+
+[Reference](http://wiki.pchart.net/doc.draw.radar.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Radar;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Prepare some nice data & axis config */
+$data = new Data();
+$data->addPoints([40, 20, 15, 10, 8, 4], "ScoreA");
+$data->addPoints([8, 10, 12, 20, 30, 15], "ScoreB");
+$data->setSerieDescription("ScoreA", "Application A");
+$data->setSerieDescription("ScoreB", "Application B");
+
+/* Create the X serie */
+$data->addPoints(["Size", "Speed", "Reliability", "Functionalities", "Ease of use", "Weight"], "Labels");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw a solid background */
+$settings = ["R" => 179, "G" => 217, "B" => 91, "Dash" => 1, "DashR" => 199, "DashG" => 237, "DashB" => 111];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay some gradient areas */
+$settings = ["StartR" => 194, "StartG" => 231, "StartB" => 44, "EndR" => 43, "EndG" => 107, "EndB" => 58, "Alpha" => 50];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Draw the border */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pRadar - Draw radar charts", ["R" => 255, "G" => 255,
+    "B" => 255]);
+
+/* Define general drawing parameters */
+$image->setFontProperties([
+    "FontName" => "Forgotte.ttf",
+    "FontSize" => 10,
+    "R" => 80,
+    "G" => 80,
+    "B" => 80
+]);
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Create the radar object */
+$radarChart = new Radar();
+
+/* Draw the 1st radar chart */
+$image->setGraphArea(10, 25, 340, 225);
+$Options = ["Layout" => RADAR_LAYOUT_STAR, "BackgroundGradient" => [
+    "StartR" => 255,
+    "StartG" => 255,
+    "StartB" => 255,
+    "StartAlpha" => 100,
+    "EndR" => 207,
+    "EndG" => 227,
+    "EndB" => 125,
+    "EndAlpha" => 50
+]];
+$radarChart->drawRadar($image, $data, $Options);
+
+/* Draw the 2nd radar chart */
+$image->setGraphArea(350, 25, 690, 225);
+$Options = ["Layout" => RADAR_LAYOUT_CIRCLE, "LabelPos" => RADAR_LABELS_HORIZONTAL, "BackgroundGradient" => [
+    "StartR" => 255,
+    "StartG" => 255,
+    "StartB" => 255,
+    "StartAlpha" => 50,
+    "EndR" => 32,
+    "EndG" => 109,
+    "EndB" => 174,
+    "EndAlpha" => 30
+]];
+$radarChart->drawRadar($image, $data, $Options);
+
+/* Write down the legend */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawLegend(270, 205, ["Style" => LEGEND_BOX, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture */
+$image->render("drawRadar.png");
+```

+ 108 - 0
vendor/szymach/c-pchart/resources/doc/scatter_best_fit.md

@@ -0,0 +1,108 @@
+# Drawing a scatter best fit chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscatterbestfit.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(rand(1, 20) * 10 + rand(0, $i), "Probe 1");
+}
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(rand(1, 2) * 10 + rand(0, $i), "Probe 2");
+}
+$data->setAxisName(0, "X-Index");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_TOP);
+
+/* Create the Y axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints($i, "Probe 3");
+}
+$data->setSerieOnAxis("Probe 3", 1);
+$data->setAxisName(1, "Y-Index");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisPosition(1, AXIS_POSITION_LEFT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("Probe 1", "Probe 3", 0);
+$data->setScatterSerieDescription(0, "This year");
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("Probe 2", "Probe 3", 1);
+$data->setScatterSerieDescription(1, "Last Year");
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawScatterBestFit() - Linear regression", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 60, 350, 360);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale();
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterPlotChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(280, 380, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Draw the line of best fit */
+$myScatter->drawScatterBestFit();
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawScatterBestFit.png");
+```

+ 100 - 0
vendor/szymach/c-pchart/resources/doc/scatter_line.md

@@ -0,0 +1,100 @@
+# Drawing a scatter line chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscatterlineChart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(cos(deg2rad($i)) * 20, "Probe 1");
+}
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(sin(deg2rad($i)) * 20, "Probe 2");
+}
+$data->setAxisName(0, "Index");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_BOTTOM);
+
+/* Create the Y axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints($i, "Probe 3");
+}
+$data->setSerieOnAxis("Probe 3", 1);
+$data->setAxisName(1, "Degree");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisUnit(1, "°");
+$data->setAxisPosition(1, AXIS_POSITION_RIGHT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("Probe 1", "Probe 3", 0);
+$data->setScatterSerieDescription(0, "This year");
+$data->setScatterSerieTicks(0, 4);
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("Probe 2", "Probe 3", 1);
+$data->setScatterSerieDescription(1, "Last Year");
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$settings = ["StartR" => 219, "StartG" => 231, "StartB" => 139, "EndR" => 1, "EndG" => 138, "EndB" => 68, "Alpha" => 50];
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawScatterLineChart() - Draw a scatter line chart", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 50, 350, 350);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale();
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterLineChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(280, 380, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawScatterLineChart.png");
+```

+ 107 - 0
vendor/szymach/c-pchart/resources/doc/scatter_plot.md

@@ -0,0 +1,107 @@
+# Drawing a scatter plot chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscatterplotChart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(cos(deg2rad($i)) * 20, "Probe 1");
+}
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints(sin(deg2rad($i)) * 20, "Probe 2");
+}
+$data->setAxisName(0, "Index");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_BOTTOM);
+
+/* Create the Y axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 10) {
+    $data->addPoints($i, "Probe 3");
+}
+$data->setSerieOnAxis("Probe 3", 1);
+$data->setAxisName(1, "Degree");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisUnit(1, "°");
+$data->setAxisPosition(1, AXIS_POSITION_RIGHT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("Probe 1", "Probe 3", 0);
+$data->setScatterSerieDescription(0, "This year");
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("Probe 2", "Probe 3", 1);
+$data->setScatterSerieDescription(1, "Last Year");
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawScatterPlotChart() - Draw a scatter plot chart", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 50, 350, 350);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale();
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterPlotChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(260, 375, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawScatterPlotChart.png");
+```

+ 108 - 0
vendor/szymach/c-pchart/resources/doc/scatter_spline.md

@@ -0,0 +1,108 @@
+# Drawing a scatter spline chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscattersplineChart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 90) {
+    $data->addPoints(rand(1, 30), "Probe 1");
+}
+for ($i = 0; $i <= 360; $i = $i + 90) {
+    $data->addPoints(rand(1, 30), "Probe 2");
+}
+$data->setAxisName(0, "Index");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_BOTTOM);
+
+/* Create the Y axis and the binded series */
+for ($i = 0; $i <= 360; $i = $i + 90) {
+    $data->addPoints($i, "Probe 3");
+}
+$data->setSerieOnAxis("Probe 3", 1);
+$data->setAxisName(1, "Degree");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisUnit(1, "°");
+$data->setAxisPosition(1, AXIS_POSITION_RIGHT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("Probe 1", "Probe 3", 0);
+$data->setScatterSerieDescription(0, "This year");
+$data->setScatterSerieTicks(0, 4);
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("Probe 2", "Probe 3", 1);
+$data->setScatterSerieDescription(1, "Last Year");
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawScatterSplineChart() - Draw a scatter spline chart", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 50, 350, 350);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale();
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterSplineChart();
+$myScatter->drawScatterPlotChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(280, 380, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawScatterSplineChart.png");
+```

+ 116 - 0
vendor/szymach/c-pchart/resources/doc/scatter_threshold.md

@@ -0,0 +1,116 @@
+# Drawing a scatter threshold chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscatterthreshold.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+$data->createFunctionSerie("X", "1/z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setAxisName(0, "x = 1/z");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_BOTTOM);
+
+/* Create the Y axis */
+$data->createFunctionSerie("Y", "z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setSerieOnAxis("Y", 1);
+$data->setAxisName(1, "y = z");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisPosition(1, AXIS_POSITION_RIGHT);
+
+/* Create the Y axis */
+$data->createFunctionSerie("Y2", "z*z*z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setSerieOnAxis("Y2", 2);
+$data->setAxisName(2, "y = z*z*z");
+$data->setAxisXY(2, AXIS_Y);
+$data->setAxisPosition(2, AXIS_POSITION_LEFT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("X", "Y", 0);
+$data->setScatterSerieDescription(0, "Pass A");
+$data->setScatterSerieTicks(0, 4);
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("X", "Y2", 1);
+$data->setScatterSerieDescription(1, "Pass B");
+$data->setScatterSerieTicks(1, 4);
+$data->setScatterSerieColor(1, ["R" => 120, "G" => 0, "B" => 255]);
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "createFunctionSerie() - Functions computing", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 50, 350, 350);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale(["XMargin" => 10, "YMargin" => 10, "Floating" => true]);
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the 0/0 lines */
+$myScatter->drawScatterThreshold(0, ["AxisID" => 0, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 10]);
+$myScatter->drawScatterThreshold(0, ["AxisID" => 1, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 10]);
+
+/* Draw a treshold area */
+$myScatter->drawScatterThresholdArea(-0.1, 0.1, ["AreaName" => "Error zone"]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterLineChart();
+$myScatter->drawScatterPlotChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(300, 380, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.createFunctionSerie.scatter.png");
+```

+ 116 - 0
vendor/szymach/c-pchart/resources/doc/scatter_threshold_area.md

@@ -0,0 +1,116 @@
+# Drawing a scatter threshold area chart
+
+[Reference](http://wiki.pchart.net/doc.scatter.drawscatterthresholdarea.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Scatter;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Data object */
+$data = new Data();
+
+/* Create the X axis and the binded series */
+$data->createFunctionSerie("X", "1/z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setAxisName(0, "x = 1/z");
+$data->setAxisXY(0, AXIS_X);
+$data->setAxisPosition(0, AXIS_POSITION_BOTTOM);
+
+/* Create the Y axis */
+$data->createFunctionSerie("Y", "z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setSerieOnAxis("Y", 1);
+$data->setAxisName(1, "y = z");
+$data->setAxisXY(1, AXIS_Y);
+$data->setAxisPosition(1, AXIS_POSITION_RIGHT);
+
+/* Create the Y axis */
+$data->createFunctionSerie("Y2", "z*z*z", ["MinX" => -10, "MaxX" => 10, "XStep" => 1]);
+$data->setSerieOnAxis("Y2", 2);
+$data->setAxisName(2, "y = z*z*z");
+$data->setAxisXY(2, AXIS_Y);
+$data->setAxisPosition(2, AXIS_POSITION_LEFT);
+
+/* Create the 1st scatter chart binding */
+$data->setScatterSerie("X", "Y", 0);
+$data->setScatterSerieDescription(0, "Pass A");
+$data->setScatterSerieTicks(0, 4);
+$data->setScatterSerieColor(0, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Create the 2nd scatter chart binding */
+$data->setScatterSerie("X", "Y2", 1);
+$data->setScatterSerieDescription(1, "Pass B");
+$data->setScatterSerieTicks(1, 4);
+$data->setScatterSerieColor(1, ["R" => 120, "G" => 0, "B" => 255]);
+
+/* Create the Image object */
+$image = new Image(400, 400, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+/* Overlay with a gradient */
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "createFunctionSerie() - Functions computing", [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Set the graph area */
+$image->setGraphArea(50, 50, 350, 350);
+
+/* Create the Scatter chart object */
+$myScatter = new Scatter($image, $data);
+
+/* Draw the scale */
+$myScatter->drawScatterScale(["XMargin" => 10, "YMargin" => 10, "Floating" => true]);
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw the 0/0 lines */
+$myScatter->drawScatterThreshold(0, ["AxisID" => 0, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 10]);
+$myScatter->drawScatterThreshold(0, ["AxisID" => 1, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 10]);
+
+/* Draw a treshold area */
+$myScatter->drawScatterThresholdArea(-0.1, 0.1, ["AreaName" => "Error zone"]);
+
+/* Draw a scatter plot chart */
+$myScatter->drawScatterLineChart();
+$myScatter->drawScatterPlotChart();
+
+/* Draw the legend */
+$myScatter->drawScatterLegend(300, 380, ["Mode" => LEGEND_HORIZONTAL, "Style" => LEGEND_NOBORDER]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.createFunctionSerie.scatter.png");
+
+```

+ 37 - 0
vendor/szymach/c-pchart/resources/doc/spline.md

@@ -0,0 +1,37 @@
+# Drawing a spline chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawsplinechart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+// Create and populate data
+$data = new Data();
+$data->addPoints([], "Serie1");
+
+// Create the image and set the data
+$image = new Image(700, 230, $data);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]);
+
+// 1st spline drawn in white with control points visible
+$firstCoordinates = [[40, 80], [280, 60], [340, 166], [590, 120]];
+$fistSplineSettings = ["R" => 255, "G" => 255, "B" => 255, "ShowControl" => true];
+$image->drawSpline($firstCoordinates, $fistSplineSettings);
+
+// 2nd spline dashed drawn in white with control points visible
+$secondCoordinates = [[250, 50], [250, 180], [350, 180], [350, 50]];
+$secondSplineSettings = [
+    "R" => 255,
+    "G" => 255,
+    "B"=> 255,
+    "ShowControl" => true,
+    "Ticks" => 4
+];
+$image->drawSpline($secondCoordinates, $secondSplineSettings);
+
+// Render the picture (choose the best way)
+$image->autoOutput("example.drawSpline.png");
+```

+ 74 - 0
vendor/szymach/c-pchart/resources/doc/split_path.md

@@ -0,0 +1,74 @@
+# Drawing a split path chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawsplitpath.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+use CpChart\Chart\Split;
+
+/* Create the Image object */
+$data = new Image(700, 230);
+
+/* Draw the background */
+$settings = [
+    "R" => 170,
+    "G" => 183,
+    "B" => 87,
+    "Dash" => 1,
+    "DashR" => 190,
+    "DashG" => 203,
+    "DashB" => 107
+];
+$data->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = ["StartR" => 219, "StartG" => 231, "StartB" => 139, "EndR" => 1,
+    "EndG" => 138, "EndB" => 68, "Alpha" => 50];
+$data->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$data->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, ["StartR" => 0, "StartG" => 0,
+    "StartB" => 0, "EndR" => 50, "EndG" => 50, "EndB" => 50, "Alpha" => 80]);
+
+/* Add a border to the picture */
+$data->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$data->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$data->drawText(10, 13, "pSplit - Draw splitted path charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Set the default font properties */
+$data->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 10, "R" => 80, "G" => 80, "B" => 80]);
+
+/* Enable shadow computing */
+$data->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([30, 20, 15, 10, 8, 4], "Score");
+$data->addPoints(["End of visit", "Home Page", "Product Page", "Sales", "Statistics", "Prints"], "Labels");
+$data->setAbscissa("Labels");
+
+/* Create the pSplit object */
+$splitChart = new Split();
+
+/* Draw the split chart */
+$settings = ["TextPos" => TEXT_POS_RIGHT, "TextPadding" => 10, "Spacing" => 20, "Surrounding" => 40];
+$data->setGraphArea(10, 20, 340, 230);
+$splitChart->drawSplitPath($data, $data, $settings);
+
+/* Create and populate the Data object */
+$data2 = new Data();
+$data2->addPoints([30, 20, 15], "Score");
+$data2->addPoints(["UK", "FR", "ES"], "Labels");
+$data2->setAbscissa("Labels");
+
+/* Draw the split chart */
+$settings = ["TextPadding" => 4, "Spacing" => 30, "Surrounding" => 20];
+$data->setGraphArea(350, 50, 690, 200);
+$splitChart->drawSplitPath($data, $data2, $settings);
+
+/* Render the picture (choose the best way) */
+$data->autoOutput("example.split.png");
+```

+ 62 - 0
vendor/szymach/c-pchart/resources/doc/spring.md

@@ -0,0 +1,62 @@
+# Drawing a spring chart
+
+[Reference](http://wiki.pchart.net/doc.spring.drawspring.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Spring;
+use CpChart\Image;
+
+/* Create the Image object */
+$image = new Image(300, 300);
+
+/* Background customization */
+$image->drawGradientArea(0, 0, 300, 300, DIRECTION_HORIZONTAL, [
+    "StartR" => 217,
+    "StartG" => 250,
+    "StartB" => 116,
+    "EndR" => 181,
+    "EndG" => 209,
+    "EndB" => 27,
+    "Alpha" => 100
+]);
+$image->drawGradientArea(0, 0, 300, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+$image->drawRectangle(0, 0, 299, 299, ["R" => 0, "G" => 0, "B" => 0]);
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pSpring - Draw spring charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Prepare the graph area */
+$image->setGraphArea(20, 20, 280, 280);
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 9, "R" => 80, "G" => 80, "B" => 80]);
+$image->setShadow(true, ["X" => 2, "Y" => 2, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Create the pSpring object */
+$springChart = new Spring();
+
+/* Set the nodes default settings */
+$springChart->setNodeDefaults(["FreeZone" => 50]);
+
+/* Build random nodes & connections */
+for ($i = 0; $i <= 10; $i++) {
+    $connections = [];
+    for ($j = 0; $j <= rand(0, 1); $j++) {
+        $connections[] = rand(0, 10);
+    }
+    $springChart->addNode($i, ["Name" => "Node " . $i, "Connections" => $connections]);
+}
+
+/* Compute and draw the Spring Graph */
+$springChart->drawSpring($image, ["DrawQuietZone" => true]);
+
+/* Render the picture */
+$image->render("drawSpring3.png");
+```

+ 88 - 0
vendor/szymach/c-pchart/resources/doc/stacked_area.md

@@ -0,0 +1,88 @@
+# Drawing a stacked area chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawstackedareachart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([1, -2, -1, 2, 1, 0], "Probe 1");
+$data->addPoints([1, -2, -3, 2, 1, 8], "Probe 2");
+$data->addPoints([2, 4, 2, 0, 4, 2], "Probe 3");
+$data->setSerieTicks("Probe 2", 4);
+$data->setAxisName(0, "Temperatures");
+$data->addPoints(["Jan", "Feb", "Mar", "Apr", "May", "Jun"], "Labels");
+$data->setSerieDescription("Labels", "Months");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawStackedAreaChart() - draw a stacked area chart", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(250, 55, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Draw the scale and the 1st chart */
+$image->setGraphArea(60, 60, 450, 190);
+$image->drawFilledRectangle(60, 60, 450, 190, ["R" => 255, "G" => 255, "B" => 255,
+    "Surrounding" => -200, "Alpha" => 10]);
+$image->drawScale(["DrawSubTicks" => true, "Mode" => SCALE_MODE_ADDALL]);
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->setShadow(false);
+$image->drawStackedAreaChart(["DisplayValues" => true, "DisplayColor" => DISPLAY_AUTO, "Surrounding" => 20]);
+
+/* Draw the scale and the 2nd chart */
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "Mode" => SCALE_MODE_ADDALL, "DrawSubTicks" => true]);
+$image->setShadow(false);
+$image->drawStackedAreaChart(["Surrounding" => 10]);
+
+/* Write the chart legend */
+$image->drawLegend(510, 205, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawStackedAreaChart.png");
+```

+ 87 - 0
vendor/szymach/c-pchart/resources/doc/stacked_bar.md

@@ -0,0 +1,87 @@
+# Drawing a stacked bar chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawstackedbarchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([-7, -8, -15, -20, -18, -12, 8, -19, 9, 16, -20, 8, 10, -10, -14, -20, 8, -9, -19], "Probe 3");
+$data->addPoints([19, 0, -8, 8, -8, 12, -19, -10, 5, 12, -20, -8, 10, -11, -12, 8, -17, -14, 0], "Probe 4");
+$data->setAxisName(0, "Temperatures");
+$data->addPoints([4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], "Time");
+$data->setSerieDescription("Time", "Hour of the day");
+$data->setAbscissa("Time");
+$data->setXAxisUnit("h");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = [
+    "R" => 170,
+    "G" => 183,
+    "B" => 87,
+    "Dash" => 1,
+    "DashR" => 190,
+    "DashG" => 203,
+    "DashB" => 107
+];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138,
+    "EndB" => 68,
+    "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+
+/* Set the default font properties */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Draw the scale */
+$image->setGraphArea(60, 30, 650, 190);
+$image->drawScale([
+    "CycleBackground" => true,
+    "DrawSubTicks" => true,
+    "GridR" => 0,
+    "GridG" => 0,
+    "GridB" => 0,
+    "GridAlpha" => 10,
+    "Mode" => SCALE_MODE_ADDALL
+]);
+
+/* Turn on shadow computing */
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+
+/* Draw some thresholds */
+$image->setShadow(false);
+$image->drawThreshold(-40, ["WriteCaption" => true, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 4]);
+$image->drawThreshold(28, ["WriteCaption" => true, "R" => 0, "G" => 0, "B" => 0, "Ticks" => 4]);
+
+/* Draw the chart */
+$image->drawStackedBarChart([
+    "Rounded" => true,
+    "DisplayValues" => true,
+    "DisplayColor" => DISPLAY_AUTO,
+    "DisplaySize" => 6,
+    "BorderR" => 255,
+    "BorderG" => 255,
+    "BorderB" => 255
+]);
+
+/* Write the chart legend */
+$image->drawLegend(570, 212, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawStackedBarChart.rounded.png");
+```

+ 93 - 0
vendor/szymach/c-pchart/resources/doc/step.md

@@ -0,0 +1,93 @@
+# Drawing a step chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawstepchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([-4, VOID, VOID, 12, 8, 3], "Probe 1");
+$data->addPoints([3, 12, 15, 8, 5, -5], "Probe 2");
+$data->addPoints([2, 7, 5, 18, 19, 22], "Probe 3");
+$data->setSerieTicks("Probe 2", 4);
+$data->setAxisName(0, "Temperatures");
+$data->addPoints(["Jan", "Feb", "Mar", "Apr", "May", "Jun"], "Labels");
+$data->setSerieDescription("Labels", "Months");
+$data->setAbscissa("Labels");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = [
+    "StartR" => 219,
+    "StartG" => 231,
+    "StartB" => 139,
+    "EndR" => 1,
+    "EndG" => 138, "EndB" => 68, "Alpha" => 50
+];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+$image->drawGradientArea(0, 0, 700, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 80
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "drawStepChart() - draw a step chart", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(250, 55, "Average temperature", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Draw the scale and the 1st chart */
+$image->setGraphArea(60, 60, 450, 190);
+$image->drawFilledRectangle(60, 60, 450, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$image->drawStepChart(["DisplayValues" => true, "DisplayColor" => DISPLAY_AUTO]);
+$image->setShadow(false);
+
+/* Draw the scale and the 2nd chart */
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "DrawSubTicks" => true]);
+$image->setShadow(true, ["X" => -1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 10]);
+$image->drawStepChart();
+$image->setShadow(false);
+
+/* Write the chart legend */
+$image->drawLegend(510, 205, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawStepChart.png");
+```

+ 78 - 0
vendor/szymach/c-pchart/resources/doc/stock.md

@@ -0,0 +1,78 @@
+# Drawing a stock chart
+
+[Reference](http://wiki.pchart.net/doc.stocks.drawstockchart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Stock;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+$data->addPoints([34, 55, 15, 62, 38, 42], "Open");
+$data->addPoints([42, 25, 40, 38, 49, 36], "Close");
+$data->addPoints([27, 14, 12, 25, 32, 32], "Min");
+$data->addPoints([45, 59, 47, 65, 64, 48], "Max");
+$data->setAxisDisplay(0, AXIS_FORMAT_CURRENCY, "$");
+$data->addPoints(["8h", "10h", "12h", "14h", "16h", "18h"], "Time");
+$data->setAbscissa("Time");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Draw the background */
+$settings = ["R" => 170, "G" => 183, "B" => 87, "Dash" => 1, "DashR" => 190, "DashG" => 203, "DashB" => 107];
+$image->drawFilledRectangle(0, 0, 700, 230, $settings);
+
+/* Overlay with a gradient */
+$settings = ["StartR" => 219, "StartG" => 231, "StartB" => 139, "EndR" => 1, "EndG" => 138, "EndB" => 68, "Alpha" => 50];
+$image->drawGradientArea(0, 0, 700, 230, DIRECTION_VERTICAL, $settings);
+
+/* Draw the border */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(60, 45, "Stock price", ["FontSize" => 28, "Align" => TEXT_ALIGN_BOTTOMLEFT]);
+
+/* Draw the 1st scale */
+$image->setGraphArea(60, 60, 450, 190);
+$image->drawFilledRectangle(60, 60, 450, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["DrawSubTicks" => true, "CycleBackground" => true]);
+
+/* Draw the 1st stock chart */
+$mystockChart = new Stock($image, $data);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 30]);
+$mystockChart->drawStockChart();
+
+/* Reset the display mode because of the graph small size */
+$data->setAxisDisplay(0, AXIS_FORMAT_DEFAULT);
+
+/* Draw the 2nd scale */
+$image->setShadow(false);
+$image->setGraphArea(500, 60, 670, 190);
+$image->drawFilledRectangle(500, 60, 670, 190, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 10
+]);
+$image->drawScale(["Pos" => SCALE_POS_TOPBOTTOM, "DrawSubTicks" => true]);
+
+/* Draw the 2nd stock chart */
+$mystockChart = new Stock($image, $data);
+$image->setShadow(true, ["X" => 1, "Y" => 1, "R" => 0, "G" => 0, "B" => 0, "Alpha" => 30]);
+$mystockChart->drawStockChart();
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawStockChart.png");
+```

+ 81 - 0
vendor/szymach/c-pchart/resources/doc/surface.md

@@ -0,0 +1,81 @@
+# Drawing a surface chart
+
+[Reference](http://wiki.pchart.net/doc.surface.drawsurface.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Chart\Surface;
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create the Image object */
+$image = new Image(400, 400);
+
+/* Create a solid background */
+$settings = ["R" => 179, "G" => 217, "B" => 91, "Dash" => 1, "DashR" => 199, "DashG" => 237, "DashB" => 111];
+$image->drawFilledRectangle(0, 0, 400, 400, $settings);
+
+$image->drawGradientArea(0, 0, 400, 400, DIRECTION_VERTICAL, [
+    "StartR" => 194,
+    "StartG" => 231,
+    "StartB" => 44,
+    "EndR" => 43,
+    "EndG" => 107,
+    "EndB" => 58,
+    "Alpha" => 50
+]);
+$image->drawGradientArea(0, 0, 400, 20, DIRECTION_VERTICAL, [
+    "StartR" => 0,
+    "StartG" => 0,
+    "StartB" => 0,
+    "EndR" => 50,
+    "EndG" => 50,
+    "EndB" => 50,
+    "Alpha" => 100
+]);
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 399, 399, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the picture title */
+$image->setFontProperties(["FontName" => "Silkscreen.ttf", "FontSize" => 6]);
+$image->drawText(10, 13, "pSurface() :: 2D surface charts", ["R" => 255, "G" => 255, "B" => 255]);
+
+/* Define the charting area */
+$image->setGraphArea(20, 40, 380, 380);
+$image->drawFilledRectangle(20, 40, 380, 380, [
+    "R" => 255,
+    "G" => 255,
+    "B" => 255,
+    "Surrounding" => -200,
+    "Alpha" => 20
+]);
+
+$image->setShadow(true, ["X" => 1, "Y" => 1]);
+
+/* Create the surface object */
+$surfaceChart = new Surface($image);
+
+/* Set the grid size */
+$surfaceChart->setGrid(20, 20);
+
+/* Write the axis labels */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+$surfaceChart->writeXLabels();
+$surfaceChart->writeYLabels();
+
+/* Add random values */
+for ($i = 0; $i <= 50; $i++) {
+    $surfaceChart->addPoint(rand(0, 20), rand(0, 20), rand(0, 100));
+}
+
+/* Compute the missing points */
+$surfaceChart->computeMissing();
+
+/* Draw the surface chart */
+$surfaceChart->drawSurface(["Border" => true, "Surrounding" => 40]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.surface.png");
+```

+ 71 - 0
vendor/szymach/c-pchart/resources/doc/zone.md

@@ -0,0 +1,71 @@
+# Drawing a zone chart
+
+[Reference](http://wiki.pchart.net/doc.chart.drawzonechart.html)
+
+```php
+require '/path/to/your/vendor/autoload.php';
+
+use CpChart\Data;
+use CpChart\Image;
+
+/* Create and populate the Data object */
+$data = new Data();
+for ($i = 0; $i <= 10; $i = $i + .2) {
+    $data->addPoints(log($i + 1) * 10, "Bounds 1");
+    $data->addPoints(log($i + 3) * 10 + rand(0, 2) - 1, "Probe");
+    $data->addPoints(log($i + 6) * 10, "Bounds 2");
+    $data->addPoints($i * 10, "Labels");
+}
+$data->setAxisName(0, "Size (cm)");
+$data->setSerieDescription("Labels", "Months");
+$data->setAbscissa("Labels");
+$data->setAbscissaName("Time (years)");
+
+/* Create the Image object */
+$image = new Image(700, 230, $data);
+
+/* Turn off Antialiasing */
+$image->Antialias = false;
+
+/* Add a border to the picture */
+$image->drawRectangle(0, 0, 699, 229, ["R" => 0, "G" => 0, "B" => 0]);
+
+/* Write the chart title */
+$image->setFontProperties(["FontName" => "Forgotte.ttf", "FontSize" => 11]);
+$image->drawText(150, 35, "Size by time generations", ["FontSize" => 20, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]);
+
+/* Set the default font */
+$image->setFontProperties(["FontName" => "pf_arma_five.ttf", "FontSize" => 6]);
+
+/* Define the chart area */
+$image->setGraphArea(40, 40, 680, 200);
+
+/* Draw the scale */
+$image->drawScale([
+    "LabelSkip" => 4,
+    "XMargin" => 10,
+    "YMargin" => 10,
+    "Floating" => true,
+    "GridR" => 200,
+    "GridG" => 200,
+    "GridB" => 200,
+    "DrawSubTicks" => true,
+    "CycleBackground" => true
+]);
+
+/* Turn on Antialiasing */
+$image->Antialias = true;
+
+/* Draw the line chart */
+$image->drawZoneChart("Bounds 1", "Bounds 2");
+$data->setSerieDrawable(["Bounds 1", "Bounds 2"], false);
+
+/* Draw the line chart */
+$image->drawStepChart();
+
+/* Write the chart legend */
+$image->drawLegend(640, 20, ["Style" => LEGEND_NOBORDER, "Mode" => LEGEND_HORIZONTAL]);
+
+/* Render the picture (choose the best way) */
+$image->autoOutput("example.drawZoneChart.png");
+```

二進制
vendor/szymach/c-pchart/resources/fonts/Bedizen.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/Forgotte.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/GeosansLight.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/MankSans.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/Silkscreen.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/advent_light.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/calibri.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/pf_arma_five.ttf


二進制
vendor/szymach/c-pchart/resources/fonts/verdana.ttf


+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/autumn.color

@@ -0,0 +1,6 @@
+185,106,154,100
+216,137,184,100
+156,192,137,100
+216,243,201,100
+253,232,215,100
+255,255,255,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/blind.color

@@ -0,0 +1,6 @@
+109,152,171,100
+0,39,94,100
+254,183,41,100
+168,177,184,100
+255,255,255,100
+0,0,0,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/evening.color

@@ -0,0 +1,6 @@
+242,245,237,100
+255,194,0,100
+255,91,0,100
+184,0,40,100
+132,0,46,100
+74,192,242,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/kitchen.color

@@ -0,0 +1,6 @@
+155,225,251,100
+197,239,253,100
+189,32,49,100
+35,31,32,100
+255,255,255,100
+0,98,149,100

+ 7 - 0
vendor/szymach/c-pchart/resources/palettes/light.color

@@ -0,0 +1,7 @@
+239,210,121,100
+149,203,233,100
+2,71,105,100
+175,215,117,100
+44,87,0,100
+222,157,127,100
+

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/navy.color

@@ -0,0 +1,6 @@
+25,78,132,100
+59,107,156,100
+31,36,42,100
+55,65,74,100
+96,187,34,100
+242,186,187,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/shade.color

@@ -0,0 +1,6 @@
+117,113,22,100
+174,188,33,100
+217,219,86,100
+0,71,127,100
+76,136,190,100
+141,195,233,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/spring.color

@@ -0,0 +1,6 @@
+146,123,81,100
+168,145,102,100
+128,195,28,100
+188,221,90,100
+255,121,0,100
+251,179,107,100

+ 6 - 0
vendor/szymach/c-pchart/resources/palettes/summer.color

@@ -0,0 +1,6 @@
+253,184,19,100
+246,139,31,100
+241,112,34,100
+98,194,204,100
+228,246,248,100
+238,246,108,100

+ 274 - 0
vendor/szymach/c-pchart/src/Barcode/Barcode128.php

@@ -0,0 +1,274 @@
+<?php
+
+namespace CpChart\Barcode;
+
+use CpChart\Image;
+use Exception;
+
+/**
+ *  pBarcode128 - class to create barcodes (128B)
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Barcode128
+{
+    /**
+     * @var array
+     */
+    public $Codes;
+
+    /**
+     * @var array
+     */
+    public $Reverse;
+
+    /**
+     * @var string
+     */
+    public $Result;
+
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var integer
+     */
+    public $CRC;
+
+    /**
+     * @param string $filePath
+     * @throws Exception
+     */
+    public function __construct($filePath = '')
+    {
+        $this->Codes = [];
+        $this->Reverse = [];
+        if (!file_exists($filePath)) {
+            $filePath = sprintf('%s/../../resources/barcode/128B.db', __DIR__);
+        }
+
+        $FileHandle = @fopen($filePath, "r");
+        if (!$FileHandle) {
+            throw new Exception(
+                sprintf("Cannot find barcode database (%s).", $filePath)
+            );
+        }
+
+        while (!feof($FileHandle)) {
+            $Buffer = fgets($FileHandle, 4096);
+            $Buffer = str_replace(chr(10), "", $Buffer);
+            $Buffer = str_replace(chr(13), "", $Buffer);
+            $Values = preg_split("/;/", $Buffer);
+
+            $this->Codes[$Values[1]]["ID"] = $Values[0];
+            $this->Codes[$Values[1]]["Code"] = $Values[2];
+            $this->Reverse[$Values[0]]["Code"] = $Values[2];
+            $this->Reverse[$Values[0]]["Asc"] = $Values[1];
+        }
+        fclose($FileHandle);
+    }
+
+    /**
+     * Return the projected size of a barcode
+     *
+     * @param string $TextString
+     * @param string $Format
+     * @return array
+     */
+    public function getSize($TextString, $Format = "")
+    {
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $ShowLegend = isset($Format["ShowLegend"]) ? $Format["ShowLegend"] : false;
+        $LegendOffset = isset($Format["LegendOffset"]) ? $Format["LegendOffset"] : 5;
+        $DrawArea = isset($Format["DrawArea"]) ? $Format["DrawArea"] : false;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : 12;
+        $Height = isset($Format["Height"]) ? $Format["Height"] : 30;
+
+        $TextString = $this->encode128($TextString);
+        $BarcodeLength = strlen($this->Result);
+
+        $WOffset = $DrawArea ? 20 : 0;
+        $HOffset = $ShowLegend ? $FontSize + $LegendOffset + $WOffset : 0;
+
+        $X1 = cos($Angle * PI / 180) * ($WOffset + $BarcodeLength);
+        $Y1 = sin($Angle * PI / 180) * ($WOffset + $BarcodeLength);
+
+        $X2 = $X1 + cos(($Angle + 90) * PI / 180) * ($HOffset + $Height);
+        $Y2 = $Y1 + sin(($Angle + 90) * PI / 180) * ($HOffset + $Height);
+
+
+        $AreaWidth = max(abs($X1), abs($X2));
+        $AreaHeight = max(abs($Y1), abs($Y2));
+
+        return ["Width" => $AreaWidth, "Height" => $AreaHeight];
+    }
+
+    /**
+     *
+     * @param string $Value
+     * @param string $Format
+     * @return string
+     */
+    public function encode128($Value, $Format = "")
+    {
+        $this->Result = "11010010000";
+        $this->CRC = 104;
+        $TextString = "";
+
+        for ($i = 1; $i <= strlen($Value); $i++) {
+            $CharCode = ord($this->mid($Value, $i, 1));
+            if (isset($this->Codes[$CharCode])) {
+                $this->Result = $this->Result . $this->Codes[$CharCode]["Code"];
+                $this->CRC = $this->CRC + $i * $this->Codes[$CharCode]["ID"];
+                $TextString = $TextString . chr($CharCode);
+            }
+        }
+        $this->CRC = $this->CRC - floor($this->CRC / 103) * 103;
+
+        $this->Result = $this->Result . $this->Reverse[$this->CRC]["Code"];
+        $this->Result = $this->Result . "1100011101011";
+
+        return $TextString;
+    }
+
+    /**
+     * Create the encoded string
+     * @param Image $Object
+     * @param string $Value
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     */
+    public function draw(Image $Object, $Value, $X, $Y, $Format = [])
+    {
+        $this->pChartObject = $Object;
+
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Height = isset($Format["Height"]) ? $Format["Height"] : 30;
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $ShowLegend = isset($Format["ShowLegend"]) ? $Format["ShowLegend"] : false;
+        $LegendOffset = isset($Format["LegendOffset"]) ? $Format["LegendOffset"] : 5;
+        $DrawArea = isset($Format["DrawArea"]) ? $Format["DrawArea"] : false;
+        $AreaR = isset($Format["AreaR"]) ? $Format["AreaR"] : 255;
+        $AreaG = isset($Format["AreaG"]) ? $Format["AreaG"] : 255;
+        $AreaB = isset($Format["AreaB"]) ? $Format["AreaB"] : 255;
+        $AreaBorderR = isset($Format["AreaBorderR"]) ? $Format["AreaBorderR"] : $AreaR;
+        $AreaBorderG = isset($Format["AreaBorderG"]) ? $Format["AreaBorderG"] : $AreaG;
+        $AreaBorderB = isset($Format["AreaBorderB"]) ? $Format["AreaBorderB"] : $AreaB;
+
+        $TextString = $this->encode128($Value);
+
+        if ($DrawArea) {
+            $X1 = $X + cos(($Angle - 135) * PI / 180) * 10;
+            $Y1 = $Y + sin(($Angle - 135) * PI / 180) * 10;
+
+            $X2 = $X1 + cos($Angle * PI / 180) * (strlen($this->Result) + 20);
+            $Y2 = $Y1 + sin($Angle * PI / 180) * (strlen($this->Result) + 20);
+
+            if ($ShowLegend) {
+                $X3 = $X2
+                    + cos(($Angle + 90) * PI / 180)
+                    * ($Height + $LegendOffset + $this->pChartObject->FontSize + 10)
+                ;
+                $Y3 = $Y2
+                    + sin(($Angle + 90) * PI / 180)
+                    * ($Height + $LegendOffset + $this->pChartObject->FontSize + 10)
+                ;
+            } else {
+                $X3 = $X2 + cos(($Angle + 90) * PI / 180) * ($Height + 20);
+                $Y3 = $Y2 + sin(($Angle + 90) * PI / 180) * ($Height + 20);
+            }
+
+            $X4 = $X3 + cos(($Angle + 180) * PI / 180) * (strlen($this->Result) + 20);
+            $Y4 = $Y3 + sin(($Angle + 180) * PI / 180) * (strlen($this->Result) + 20);
+
+            $Polygon = [$X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4];
+            $Settings = [
+                "R" => $AreaR,
+                "G" => $AreaG,
+                "B" => $AreaB,
+                "BorderR" => $AreaBorderR,
+                "BorderG" => $AreaBorderG,
+                "BorderB" => $AreaBorderB
+            ];
+            $this->pChartObject->drawPolygon($Polygon, $Settings);
+        }
+
+        for ($i = 1; $i <= strlen($this->Result); $i++) {
+            if ($this->mid($this->Result, $i, 1) == 1) {
+                $X1 = $X + cos($Angle * PI / 180) * $i;
+                $Y1 = $Y + sin($Angle * PI / 180) * $i;
+                $X2 = $X1 + cos(($Angle + 90) * PI / 180) * $Height;
+                $Y2 = $Y1 + sin(($Angle + 90) * PI / 180) * $Height;
+
+                $Settings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                $this->pChartObject->drawLine($X1, $Y1, $X2, $Y2, $Settings);
+            }
+        }
+
+        if ($ShowLegend) {
+            $X1 = $X + cos($Angle * PI / 180) * (strlen($this->Result) / 2);
+            $Y1 = $Y + sin($Angle * PI / 180) * (strlen($this->Result) / 2);
+
+            $LegendX = $X1 + cos(($Angle + 90) * PI / 180) * ($Height + $LegendOffset);
+            $LegendY = $Y1 + sin(($Angle + 90) * PI / 180) * ($Height + $LegendOffset);
+
+            $Settings = [
+                "R" => $R,
+                "G" => $G,
+                "B" => $B,
+                "Alpha" => $Alpha,
+                "Angle" => -$Angle,
+                "Align" => TEXT_ALIGN_TOPMIDDLE
+            ];
+            $this->pChartObject->drawText($LegendX, $LegendY, $TextString, $Settings);
+        }
+    }
+
+    /**
+     *
+     * @param string $value
+     * @param int $NbChar
+     * @return string
+     */
+    public function left($value, $NbChar)
+    {
+        return substr($value, 0, $NbChar);
+    }
+
+    /**
+     *
+     * @param string $value
+     * @param int $NbChar
+     * @return string
+     */
+    public function right($value, $NbChar)
+    {
+        return substr($value, strlen($value) - $NbChar, $NbChar);
+    }
+
+    /**
+     *
+     * @param string $value
+     * @param int $Depart
+     * @param int $NbChar
+     * @return string
+     */
+    public function mid($value, $Depart, $NbChar)
+    {
+        return substr($value, $Depart - 1, $NbChar);
+    }
+}

+ 296 - 0
vendor/szymach/c-pchart/src/Barcode/Barcode39.php

@@ -0,0 +1,296 @@
+<?php
+
+namespace CpChart\Barcode;
+
+use CpChart\Image;
+use Exception;
+
+/**
+ *  pBarcode39 - class to create barcodes (39B)
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Barcode39
+{
+    /**
+     * @var array
+     */
+    public $Codes;
+
+    /**
+     * @var array
+     */
+    public $Reverse;
+
+    /**
+     * @var string
+     */
+    public $Result;
+
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var integer
+     */
+    public $CRC;
+
+    /**
+     * @var boolean
+     */
+    public $MOD43;
+
+    /**
+     * @param string $filePath
+     * @param boolean $EnableMOD43
+     * @throws Exception
+     */
+    public function __construct($filePath = "", $EnableMOD43 = false)
+    {
+        $this->MOD43 = (boolean) $EnableMOD43;
+        $this->Codes = [];
+        $this->Reverse = [];
+        if (!file_exists($filePath)) {
+            $filePath = sprintf('%s/../../resources/barcode/39.db', __DIR__);
+        }
+
+        $FileHandle = @fopen($filePath, "r");
+        if (!$FileHandle) {
+            throw new Exception(
+                "Cannot find barcode database (" . $filePath . ")."
+            );
+        }
+
+        while (!feof($FileHandle)) {
+            $Buffer = fgets($FileHandle, 4096);
+            $Buffer = str_replace(chr(10), "", $Buffer);
+            $Buffer = str_replace(chr(13), "", $Buffer);
+            $Values = preg_split("/;/", $Buffer);
+
+            $this->Codes[$Values[0]] = $Values[1];
+        }
+        fclose($FileHandle);
+    }
+
+    /**
+     * Return the projected size of a barcode
+     *
+     * @param string $TextString
+     * @param string $Format
+     * @return array
+     */
+    public function getSize($TextString, $Format = "")
+    {
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $ShowLegend = isset($Format["ShowLegend"]) ? $Format["ShowLegend"] : false;
+        $LegendOffset = isset($Format["LegendOffset"]) ? $Format["LegendOffset"] : 5;
+        $DrawArea = isset($Format["DrawArea"]) ? $Format["DrawArea"] : false;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : 12;
+        $Height = isset($Format["Height"]) ? $Format["Height"] : 30;
+
+        $TextString = $this->encode39($TextString);
+        $BarcodeLength = strlen($this->Result);
+
+        $WOffset = $DrawArea ? 20 : 0;
+        $HOffset = $ShowLegend ? $FontSize + $LegendOffset + $WOffset : 0;
+
+        $X1 = cos($Angle * PI / 180) * ($WOffset + $BarcodeLength);
+        $Y1 = sin($Angle * PI / 180) * ($WOffset + $BarcodeLength);
+
+        $X2 = $X1 + cos(($Angle + 90) * PI / 180) * ($HOffset + $Height);
+        $Y2 = $Y1 + sin(($Angle + 90) * PI / 180) * ($HOffset + $Height);
+
+
+        $AreaWidth = max(abs($X1), abs($X2));
+        $AreaHeight = max(abs($Y1), abs($Y2));
+
+        return ["Width" => $AreaWidth, "Height" => $AreaHeight];
+    }
+
+    /**
+     * Create the encoded string
+     *
+     * @param string $Value
+     * @return string
+     */
+    public function encode39($Value)
+    {
+        $this->Result = "100101101101" . "0";
+        $TextString = "";
+        for ($i = 1; $i <= strlen($Value); $i++) {
+            $CharCode = ord($this->mid($Value, $i, 1));
+            if ($CharCode >= 97 && $CharCode <= 122) {
+                $CharCode = $CharCode - 32;
+            }
+
+            if (isset($this->Codes[chr($CharCode)])) {
+                $this->Result = $this->Result . $this->Codes[chr($CharCode)] . "0";
+                $TextString = $TextString . chr($CharCode);
+            }
+        }
+
+        if ($this->MOD43) {
+            $Checksum = $this->checksum($TextString);
+            $this->Result = $this->Result . $this->Codes[$Checksum] . "0";
+        }
+
+        $this->Result = $this->Result . "100101101101";
+        $TextString = "*" . $TextString . "*";
+
+        return $TextString;
+    }
+
+    /**
+     * Create the encoded string
+     * @param Image $Object
+     * @param type $Value
+     * @param type $X
+     * @param type $Y
+     * @param array $Format
+     */
+    public function draw(Image $Object, $Value, $X, $Y, $Format = [])
+    {
+        $this->pChartObject = $Object;
+
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Height = isset($Format["Height"]) ? $Format["Height"] : 30;
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $ShowLegend = isset($Format["ShowLegend"]) ? $Format["ShowLegend"] : false;
+        $LegendOffset = isset($Format["LegendOffset"]) ? $Format["LegendOffset"] : 5;
+        $DrawArea = isset($Format["DrawArea"]) ? $Format["DrawArea"] : false;
+        $AreaR = isset($Format["AreaR"]) ? $Format["AreaR"] : 255;
+        $AreaG = isset($Format["AreaG"]) ? $Format["AreaG"] : 255;
+        $AreaB = isset($Format["AreaB"]) ? $Format["AreaB"] : 255;
+        $AreaBorderR = isset($Format["AreaBorderR"]) ? $Format["AreaBorderR"] : $AreaR;
+        $AreaBorderG = isset($Format["AreaBorderG"]) ? $Format["AreaBorderG"] : $AreaG;
+        $AreaBorderB = isset($Format["AreaBorderB"]) ? $Format["AreaBorderB"] : $AreaB;
+
+        $TextString = $this->encode39($Value);
+
+        if ($DrawArea) {
+            $X1 = $X + cos(($Angle - 135) * PI / 180) * 10;
+            $Y1 = $Y + sin(($Angle - 135) * PI / 180) * 10;
+
+            $X2 = $X1 + cos($Angle * PI / 180) * (strlen($this->Result) + 20);
+            $Y2 = $Y1 + sin($Angle * PI / 180) * (strlen($this->Result) + 20);
+
+            if ($ShowLegend) {
+                $X3 = $X2
+                    + cos(($Angle + 90) * PI / 180)
+                    * ($Height + $LegendOffset + $this->pChartObject->FontSize + 10)
+                ;
+                $Y3 = $Y2
+                    + sin(($Angle + 90) * PI / 180)
+                    * ($Height + $LegendOffset + $this->pChartObject->FontSize + 10)
+                ;
+            } else {
+                $X3 = $X2 + cos(($Angle + 90) * PI / 180) * ($Height + 20);
+                $Y3 = $Y2 + sin(($Angle + 90) * PI / 180) * ($Height + 20);
+            }
+
+            $X4 = $X3 + cos(($Angle + 180) * PI / 180) * (strlen($this->Result) + 20);
+            $Y4 = $Y3 + sin(($Angle + 180) * PI / 180) * (strlen($this->Result) + 20);
+
+            $Polygon = [$X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4];
+            $Settings = [
+                "R" => $AreaR,
+                "G" => $AreaG,
+                "B" => $AreaB,
+                "BorderR" => $AreaBorderR,
+                "BorderG" => $AreaBorderG,
+                "BorderB" => $AreaBorderB
+            ];
+            $this->pChartObject->drawPolygon($Polygon, $Settings);
+        }
+
+        for ($i = 1; $i <= strlen($this->Result); $i++) {
+            if ($this->mid($this->Result, $i, 1) == 1) {
+                $X1 = $X + cos($Angle * PI / 180) * $i;
+                $Y1 = $Y + sin($Angle * PI / 180) * $i;
+                $X2 = $X1 + cos(($Angle + 90) * PI / 180) * $Height;
+                $Y2 = $Y1 + sin(($Angle + 90) * PI / 180) * $Height;
+
+                $Settings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                $this->pChartObject->drawLine($X1, $Y1, $X2, $Y2, $Settings);
+            }
+        }
+
+        if ($ShowLegend) {
+            $X1 = $X + cos($Angle * PI / 180) * (strlen($this->Result) / 2);
+            $Y1 = $Y + sin($Angle * PI / 180) * (strlen($this->Result) / 2);
+
+            $LegendX = $X1 + cos(($Angle + 90) * PI / 180) * ($Height + $LegendOffset);
+            $LegendY = $Y1 + sin(($Angle + 90) * PI / 180) * ($Height + $LegendOffset);
+
+            $Settings = [
+                "R" => $R,
+                "G" => $G,
+                "B" => $B,
+                "Alpha" => $Alpha,
+                "Angle" => -$Angle,
+                "Align" => TEXT_ALIGN_TOPMIDDLE
+            ];
+            $this->pChartObject->drawText($LegendX, $LegendY, $TextString, $Settings);
+        }
+    }
+
+    /**
+     * @param string $string
+     * @return string
+     */
+    public function checksum($string)
+    {
+        $checksum = 0;
+        $length = strlen($string);
+        $charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%';
+
+        for ($i = 0; $i < $length; ++$i) {
+            $checksum += strpos($charset, $string[$i]);
+        }
+        return substr($charset, ($checksum % 43), 1);
+    }
+
+    /**
+     * @param string $value
+     * @param int $NbChar
+     * @return string
+     */
+    public function left($value, $NbChar)
+    {
+        return substr($value, 0, $NbChar);
+    }
+
+    /**
+     * @param string $value
+     * @param int $NbChar
+     * @return string|false
+     */
+    public function right($value, $NbChar)
+    {
+        return substr($value, strlen($value) - $NbChar, $NbChar);
+    }
+
+    /**
+     * @param string $value
+     * @param int $Depart
+     * @param int $NbChar
+     * @return string
+     */
+    public function mid($value, $Depart, $NbChar)
+    {
+        return substr($value, $Depart - 1, $NbChar);
+    }
+}

+ 1728 - 0
vendor/szymach/c-pchart/src/BaseDraw.php

@@ -0,0 +1,1728 @@
+<?php
+
+namespace CpChart;
+
+use Exception;
+
+/**
+ * This class exists only to try and reduce the number of methods and properties
+ * in the Draw class. Basically all methods not named 'drawX' were moved in here,
+ * as well as all the class fields.
+ */
+abstract class BaseDraw
+{
+    /**
+     * Width of the picture
+     * @var int
+     */
+    public $XSize;
+
+    /**
+     * Height of the picture
+     * @var int
+     */
+    public $YSize;
+
+    /**
+     * GD picture object
+     * @var resource
+     */
+    public $Picture;
+
+    /**
+     * Turn antialias on or off
+     * @var boolean
+     */
+    public $Antialias = true;
+
+    /**
+     * Quality of the antialiasing implementation (0-1)
+     * @var int
+     */
+    public $AntialiasQuality = 0;
+
+    /**
+     * Already drawn pixels mask (Filled circle implementation)
+     * @var array
+     */
+    public $Mask = [];
+
+    /**
+     * Just to know if we need to flush the alpha channels when rendering
+     * @var boolean
+     */
+    public $TransparentBackground = false;
+
+    /**
+     * Graph area X origin
+     * @var int
+     */
+    public $GraphAreaX1;
+
+    /**
+     * Graph area Y origin
+     * @var int
+     */
+    public $GraphAreaY1;
+
+    /**
+     * Graph area bottom right X position
+     * @var int
+     */
+    public $GraphAreaX2;
+
+    /**
+     * Graph area bottom right Y position
+     * @var int
+     */
+    public $GraphAreaY2;
+
+    /**
+     * Minimum height for scale divs
+     * @var int
+     */
+    public $ScaleMinDivHeight = 20;
+
+    /**
+     * @var string
+     */
+    public $FontName = "GeosansLight.ttf";
+
+    /**
+     * @var int
+     */
+    public $FontSize = 12;
+
+    /**
+     * Return the bounding box of the last written string
+     * @var array
+     */
+    public $FontBox;
+
+    /**
+     * @var int
+     */
+    public $FontColorR = 0;
+
+    /**
+     * @var int
+     */
+    public $FontColorG = 0;
+
+    /**
+     * @var int
+     */
+    public $FontColorB = 0;
+
+    /**
+     * @var int
+     */
+    public $FontColorA = 100;
+
+    /**
+     * Turn shadows on or off
+     * @var boolean
+     */
+    public $Shadow = false;
+
+    /**
+     * X Offset of the shadow
+     * @var int
+     */
+    public $ShadowX;
+
+    /**
+     * Y Offset of the shadow
+     * @var int
+     */
+    public $ShadowY;
+
+    /**
+     * R component of the shadow
+     * @var int
+     */
+    public $ShadowR;
+
+    /**
+     * G component of the shadow
+     * @var int
+     */
+    public $ShadowG;
+
+    /**
+     * B component of the shadow
+     * @var int
+     */
+    public $ShadowB;
+
+    /**
+     * Alpha level of the shadow
+     * @var int
+     */
+    public $Shadowa;
+
+    /**
+     * Array containing the image map
+     * @var array
+     */
+    public $ImageMap = [];
+
+    /**
+     * Name of the session array
+     * @var int
+     */
+    public $ImageMapIndex = "pChart";
+
+    /**
+     * Save the current imagemap storage mode
+     * @var int
+     */
+    public $ImageMapStorageMode;
+
+    /**
+     * Automatic deletion of the image map temp files
+     * @var boolean
+     */
+    public $ImageMapAutoDelete = true;
+
+    /**
+     * Attached dataset
+     * @var Data
+     */
+    public $DataSet;
+
+    /**
+     * Last generated chart info
+     * Last layout : regular or stacked
+     * @var int
+     */
+    public $LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+    /**
+     * @var string
+     */
+    private $resourcePath;
+
+    public function __construct()
+    {
+        $this->resourcePath = sprintf('%s/../resources', __DIR__);
+        $this->FontName = $this->loadFont($this->FontName, 'fonts');
+    }
+
+    /**
+     * Set the path to the folder containing library resources (fonts, data, palettes).
+     *
+     * @param string $path
+     * @throws Exception
+     */
+    public function setResourcePath($path)
+    {
+        $escapedPath = rtrim($path, '/');
+        if (!file_exists($escapedPath)) {
+            throw new Exception(sprintf(
+                "The path '%s' to resources' folder does not exist!",
+                $escapedPath
+            ));
+        }
+
+        $this->resourcePath = $escapedPath;
+    }
+
+    /**
+     * Check if requested resource exists and return the path to it if yes.
+     * @param string $name
+     * @param string $type
+     * @return string
+     * @throws Exception
+     */
+    protected function loadFont($name, $type)
+    {
+        if (file_exists($name)) {
+            return $name;
+        }
+
+        $path = sprintf('%s/%s/%s', $this->resourcePath, $type, $name);
+        if (file_exists($path)) {
+            return $path;
+        }
+
+        throw new Exception(
+            sprintf('The requested resource %s (%s) has not been found!', $name, $type)
+        );
+    }
+
+    /**
+     * Allocate a color with transparency
+     * @param resource $Picture
+     * @param int $R
+     * @param int $G
+     * @param int $B
+     * @param int $Alpha
+     * @return int
+     */
+    public function allocateColor($Picture, $R, $G, $B, $Alpha = 100)
+    {
+        if ($R < 0) {
+            $R = 0;
+        }
+        if ($R > 255) {
+            $R = 255;
+        }
+        if ($G < 0) {
+            $G = 0;
+        }
+        if ($G > 255) {
+            $G = 255;
+        }
+        if ($B < 0) {
+            $B = 0;
+        }
+        if ($B > 255) {
+            $B = 255;
+        }
+        if ($Alpha < 0) {
+            $Alpha = 0;
+        }
+        if ($Alpha > 100) {
+            $Alpha = 100;
+        }
+
+        $Alpha = $this->convertAlpha($Alpha);
+        return imagecolorallocatealpha($Picture, (int) $R, (int) $G, (int) $B, (int) $Alpha);
+    }
+
+    /**
+     * Convert apha to base 10
+     * @param int|float $AlphaValue
+     * @return integer
+     */
+    public function convertAlpha($AlphaValue)
+    {
+        return floor((127 / 100) * (100 - $AlphaValue));
+    }
+
+    /**
+     * @param string $FileName
+     * @return array
+     */
+    public function getPicInfo($FileName)
+    {
+        $Infos = getimagesize($FileName);
+        $Width = $Infos[0];
+        $Height = $Infos[1];
+        $Type = $Infos["mime"];
+
+        if ($Type == "image/png") {
+            $Type = 1;
+        }
+        if ($Type == "image/gif") {
+            $Type = 2;
+        }
+        if ($Type == "image/jpeg ") {
+            $Type = 3;
+        }
+
+        return [$Width, $Height, $Type];
+    }
+
+    /**
+     * Compute the scale, check for the best visual factors
+     * @param int $XMin
+     * @param int $XMax
+     * @param int $MaxDivs
+     * @param array $Factors
+     * @param int $AxisID
+     * @return mixed
+     */
+    public function computeScale($XMin, $XMax, $MaxDivs, array $Factors, $AxisID = 0)
+    {
+        /* Compute each factors */
+        $Results = [];
+        foreach ($Factors as $Key => $Factor) {
+            $Results[$Factor] = $this->processScale($XMin, $XMax, $MaxDivs, [$Factor], $AxisID);
+        }
+        /* Remove scales that are creating to much decimals */
+        $GoodScaleFactors = [];
+        foreach ($Results as $Key => $Result) {
+            $Decimals = preg_split("/\./", $Result["RowHeight"]);
+            if ((!isset($Decimals[1])) || (strlen($Decimals[1]) < 6)) {
+                $GoodScaleFactors[] = $Key;
+            }
+        }
+
+        /* Found no correct scale, shame,... returns the 1st one as default */
+        if (!count($GoodScaleFactors)) {
+            return $Results[$Factors[0]];
+        }
+
+        /* Find the factor that cause the maximum number of Rows */
+        $MaxRows = 0;
+        $BestFactor = 0;
+        foreach ($GoodScaleFactors as $Key => $Factor) {
+            if ($Results[$Factor]["Rows"] > $MaxRows) {
+                $MaxRows = $Results[$Factor]["Rows"];
+                $BestFactor = $Factor;
+            }
+        }
+
+        /* Return the best visual scale */
+        return $Results[$BestFactor];
+    }
+
+    /**
+     * Compute the best matching scale based on size & factors
+     * @param int $XMin
+     * @param int $XMax
+     * @param int $MaxDivs
+     * @param array $Factors
+     * @param int $AxisID
+     * @return array
+     */
+    public function processScale($XMin, $XMax, $MaxDivs, array $Factors, $AxisID)
+    {
+        $ScaleHeight = abs(ceil($XMax) - floor($XMin));
+
+        $Format = null;
+        if (isset($this->DataSet->Data["Axis"][$AxisID]["Format"])) {
+            $Format = $this->DataSet->Data["Axis"][$AxisID]["Format"];
+        }
+
+        $Mode = AXIS_FORMAT_DEFAULT;
+        if (isset($this->DataSet->Data["Axis"][$AxisID]["Display"])) {
+            $Mode = $this->DataSet->Data["Axis"][$AxisID]["Display"];
+        }
+
+        $Scale = [];
+        if ($XMin != $XMax) {
+            $Found = false;
+            $Rescaled = false;
+            $Scaled10Factor = .0001;
+            $Result = 0;
+            while (!$Found) {
+                foreach ($Factors as $Key => $Factor) {
+                    if (!$Found) {
+                        $XMinRescaled = $XMin;
+                        if (!($this->modulo($XMin, $Factor * $Scaled10Factor) == 0)
+                            || ($XMin != floor($XMin))
+                        ) {
+                            $XMinRescaled = floor($XMin / ($Factor * $Scaled10Factor))
+                                * $Factor
+                                * $Scaled10Factor
+                            ;
+                        }
+
+                        $XMaxRescaled = $XMax;
+                        if (!($this->modulo($XMax, $Factor * $Scaled10Factor) == 0)
+                            || ($XMax != floor($XMax))
+                        ) {
+                            $XMaxRescaled = floor($XMax / ($Factor * $Scaled10Factor))
+                                * $Factor
+                                * $Scaled10Factor
+                                + ($Factor * $Scaled10Factor)
+                            ;
+                        }
+
+                        $ScaleHeightRescaled = abs($XMaxRescaled - $XMinRescaled);
+
+                        if (!$Found
+                            && floor($ScaleHeightRescaled / ($Factor * $Scaled10Factor)) <= $MaxDivs
+                        ) {
+                            $Found = true;
+                            $Rescaled = true;
+                            $Result = $Factor * $Scaled10Factor;
+                        }
+                    }
+                }
+                $Scaled10Factor = $Scaled10Factor * 10;
+            }
+
+            /* ReCall Min / Max / Height */
+            if ($Rescaled) {
+                $XMin = $XMinRescaled;
+                $XMax = $XMaxRescaled;
+                $ScaleHeight = $ScaleHeightRescaled;
+            }
+
+            /* Compute rows size */
+            $Rows = floor($ScaleHeight / $Result);
+            if ($Rows == 0) {
+                $Rows = 1;
+            }
+            $RowHeight = $ScaleHeight / $Rows;
+
+            /* Return the results */
+            $Scale["Rows"] = $Rows;
+            $Scale["RowHeight"] = $RowHeight;
+            $Scale["XMin"] = $XMin;
+            $Scale["XMax"] = $XMax;
+
+            /* Compute the needed decimals for the metric view to avoid repetition of the same X Axis labels */
+            if ($Mode == AXIS_FORMAT_METRIC && $Format == null) {
+                $Done = false;
+                $GoodDecimals = 0;
+                for ($Decimals = 0; $Decimals <= 10; $Decimals++) {
+                    if (!$Done) {
+                        $LastLabel = "zob";
+                        $ScaleOK = true;
+                        for ($i = 0; $i <= $Rows; $i++) {
+                            $Value = $XMin + $i * $RowHeight;
+                            $Label = $this->scaleFormat($Value, AXIS_FORMAT_METRIC, $Decimals);
+
+                            if ($LastLabel == $Label) {
+                                $ScaleOK = false;
+                            }
+                            $LastLabel = $Label;
+                        }
+                        if ($ScaleOK) {
+                            $Done = true;
+                            $GoodDecimals = $Decimals;
+                        }
+                    }
+                }
+                $Scale["Format"] = $GoodDecimals;
+            }
+        } else {
+            /* If all values are the same we keep a +1/-1 scale */
+            $Rows = 2;
+            $XMin = $XMax - 1;
+            $XMax = $XMax + 1;
+            $RowHeight = 1;
+
+            /* Return the results */
+            $Scale["Rows"] = $Rows;
+            $Scale["RowHeight"] = $RowHeight;
+            $Scale["XMin"] = $XMin;
+            $Scale["XMax"] = $XMax;
+        }
+
+        return $Scale;
+    }
+
+    /**
+     *
+     * @param int|float $Value1
+     * @param int|float $Value2
+     * @return double
+     */
+    public function modulo($Value1, $Value2)
+    {
+        if (floor($Value2) == 0) {
+            return 0;
+        }
+        if (floor($Value2) != 0) {
+            return (int) $Value1 % (int) $Value2;
+        }
+
+        $MinValue = min($Value1, $Value2);
+        $Factor = 10;
+        while (floor($MinValue * $Factor) == 0) {
+            $Factor = $Factor * 10;
+        }
+
+        return floor($Value1 * $Factor) % floor($Value2 * $Factor);
+    }
+
+    /**
+     * @param mixed $Value
+     * @param mixed $LastValue
+     * @param integer $LabelingMethod
+     * @param integer $ID
+     * @param boolean $LabelSkip
+     * @return boolean
+     */
+    public function isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
+    {
+        if ($LabelingMethod == LABELING_DIFFERENT && $Value != $LastValue) {
+            return true;
+        }
+        if ($LabelingMethod == LABELING_DIFFERENT && $Value == $LastValue) {
+            return false;
+        }
+        if ($LabelingMethod == LABELING_ALL && $LabelSkip == 0) {
+            return true;
+        }
+        if ($LabelingMethod == LABELING_ALL && ($ID + $LabelSkip) % ($LabelSkip + 1) != 1) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the number of drawable series
+     * @return int
+     */
+    public function countDrawableSeries()
+    {
+        $count = 0;
+        $Data = $this->DataSet->getData();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $count++;
+            }
+        }
+
+        return $count;
+    }
+
+    /**
+     * Fix box coordinates
+     * @param int $Xa
+     * @param int $Ya
+     * @param int $Xb
+     * @param int $Yb
+     * @return integer[]
+     */
+    public function fixBoxCoordinates($Xa, $Ya, $Xb, $Yb)
+    {
+        return [
+            (int) min($Xa, $Xb),
+            (int) min($Ya, $Yb),
+            (int) max($Xa, $Xb),
+            (int) max($Ya, $Yb)
+        ];
+    }
+
+    /**
+     * Apply AALias correction to the rounded box boundaries
+     * @param int|float $Value
+     * @param int $Mode
+     * @return int|float
+     */
+    public function offsetCorrection($Value, $Mode)
+    {
+        $Value = round($Value, 1);
+
+        if ($Value == 0 && $Mode != 1) {
+            return 0;
+        }
+
+        if ($Mode == 1) {
+            if ($Value == .5) {
+                return .5;
+            }
+            if ($Value == .8) {
+                return .6;
+            }
+            if (in_array($Value, [.4, .7])) {
+                return .7;
+            }
+            if (in_array($Value, [.2, .3, .6])) {
+                return .8;
+            }
+            if (in_array($Value, [0, 1, .1, .9])) {
+                return .9;
+            }
+        }
+
+        if ($Mode == 2) {
+            if ($Value == .1) {
+                return .1;
+            }
+            if ($Value == .2) {
+                return .2;
+            }
+            if ($Value == .3) {
+                return .3;
+            }
+            if ($Value == .4) {
+                return .4;
+            }
+            if ($Value == .5) {
+                return .5;
+            }
+            if ($Value == .7) {
+                return .7;
+            }
+            if (in_array($Value, [.6, .8])) {
+                return .8;
+            }
+            if (in_array($Value, [1, .9])) {
+                return .9;
+            }
+        }
+
+        if ($Mode == 3) {
+            if (in_array($Value, [1, .1])) {
+                return .1;
+            }
+            if ($Value == .2) {
+                return .2;
+            }
+            if ($Value == .3) {
+                return .3;
+            }
+            if (in_array($Value, [.4, .8])) {
+                return .4;
+            }
+            if ($Value == .5) {
+                return .9;
+            }
+            if ($Value == .6) {
+                return .6;
+            }
+            if ($Value == .7) {
+                return .7;
+            }
+            if ($Value == .9) {
+                return .5;
+            }
+        }
+
+        if ($Mode == 4) {
+            if ($Value == 1) {
+                return -1;
+            }
+            if (in_array($Value, [.1, .4, .7, .8, .9])) {
+                return .1;
+            }
+            if ($Value == .2) {
+                return .2;
+            }
+            if ($Value == .3) {
+                return .3;
+            }
+            if ($Value == .5) {
+                return -.1;
+            }
+            if ($Value == .6) {
+                return .8;
+            }
+        }
+    }
+
+    /**
+     * Get the legend box size
+     * @param array $Format
+     * @return array
+     */
+    public function getLegendSize(array $Format = [])
+    {
+        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
+        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 5;
+        $BoxHeight = isset($Format["BoxHeight"]) ? $Format["BoxHeight"] : 5;
+        $IconAreaWidth = isset($Format["IconAreaWidth"]) ? $Format["IconAreaWidth"] : $BoxWidth;
+        $IconAreaHeight = isset($Format["IconAreaHeight"]) ? $Format["IconAreaHeight"] : $BoxHeight;
+        $XSpacing = isset($Format["XSpacing"]) ? $Format["XSpacing"] : 5;
+
+        $Data = $this->DataSet->getData();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true
+                && $SerieName != $Data["Abscissa"]
+                && isset($Serie["Picture"])
+            ) {
+                list($PicWidth, $PicHeight) = $this->getPicInfo($Serie["Picture"]);
+                if ($IconAreaWidth < $PicWidth) {
+                    $IconAreaWidth = $PicWidth;
+                }
+                if ($IconAreaHeight < $PicHeight) {
+                    $IconAreaHeight = $PicHeight;
+                }
+            }
+        }
+
+        $YStep = max($this->FontSize, $IconAreaHeight) + 5;
+        $XStep = $IconAreaWidth + 5;
+        $XStep = $XSpacing;
+
+        $X = 100;
+        $Y = 100;
+
+        $Boundaries = [];
+        $Boundaries["L"] = $X;
+        $Boundaries["T"] = $Y;
+        $Boundaries["R"] = 0;
+        $Boundaries["B"] = 0;
+        $vY = $Y;
+        $vX = $X;
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                if ($Mode == LEGEND_VERTICAL) {
+                    $BoxArray = $this->getTextBox(
+                        $vX + $IconAreaWidth + 4,
+                        $vY + $IconAreaHeight / 2,
+                        $FontName,
+                        $FontSize,
+                        0,
+                        $Serie["Description"]
+                    );
+
+                    if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                        $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                    }
+                    if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                        $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                    }
+                    if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                        $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                    }
+
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    $vY = $vY + max($this->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->getTextBox(
+                            $vX + $IconAreaWidth + 6,
+                            $Y + $IconAreaHeight / 2 + (($this->FontSize + 3) * $Key),
+                            $FontName,
+                            $FontSize,
+                            0,
+                            $Value
+                        );
+
+                        if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                            $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                        }
+                        if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                            $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                        }
+                        if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                            $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                        }
+
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+
+                    $vX = max($Width) + $XStep;
+                }
+            }
+        }
+        $vY = $vY - $YStep;
+        $vX = $vX - $XStep;
+
+        $TopOffset = $Y - $Boundaries["T"];
+        if ($Boundaries["B"] - ($vY + $IconAreaHeight) < $TopOffset) {
+            $Boundaries["B"] = $vY + $IconAreaHeight + $TopOffset;
+        }
+
+        $Width = ($Boundaries["R"] + $Margin) - ($Boundaries["L"] - $Margin);
+        $Height = ($Boundaries["B"] + $Margin) - ($Boundaries["T"] - $Margin);
+
+        return ["Width" => $Width, "Height" => $Height];
+    }
+
+    /**
+     * Return the abscissa margin
+     * @param array $Data
+     * @return int
+     */
+    public function getAbscissaMargin(array $Data)
+    {
+        foreach ($Data["Axis"] as $Values) {
+            if ($Values["Identity"] == AXIS_X) {
+                return $Values["Margin"];
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a random color
+     * @param int $Alpha
+     * @return array
+     */
+    public function getRandomColor($Alpha = 100)
+    {
+        return [
+            "R" => rand(0, 255),
+            "G" => rand(0, 255),
+            "B" => rand(0, 255),
+            "Alpha" => $Alpha
+        ];
+    }
+
+    /**
+     * Validate a palette
+     * @param mixed $Colors
+     * @param int|float $Surrounding
+     * @return array
+     */
+    public function validatePalette($Colors, $Surrounding = null)
+    {
+        $Result = [];
+
+        if (!is_array($Colors)) {
+            return $this->getRandomColor();
+        }
+
+        foreach ($Colors as $Key => $Values) {
+            if (isset($Values["R"])) {
+                $Result[$Key]["R"] = $Values["R"];
+            } else {
+                $Result[$Key]["R"] = rand(0, 255);
+            }
+
+            if (isset($Values["G"])) {
+                $Result[$Key]["G"] = $Values["G"];
+            } else {
+                $Result[$Key]["G"] = rand(0, 255);
+            }
+
+            if (isset($Values["B"])) {
+                $Result[$Key]["B"] = $Values["B"];
+            } else {
+                $Result[$Key]["B"] = rand(0, 255);
+            }
+            if (isset($Values["Alpha"])) {
+                $Result[$Key]["Alpha"] = $Values["Alpha"];
+            } else {
+                $Result[$Key]["Alpha"] = 100;
+            }
+
+            if (null !== $Surrounding) {
+                $Result[$Key]["BorderR"] = $Result[$Key]["R"] + $Surrounding;
+                $Result[$Key]["BorderG"] = $Result[$Key]["G"] + $Surrounding;
+                $Result[$Key]["BorderB"] = $Result[$Key]["B"] + $Surrounding;
+            } else {
+                if (isset($Values["BorderR"])) {
+                    $Result[$Key]["BorderR"] = $Values["BorderR"];
+                } else {
+                    $Result[$Key]["BorderR"] = $Result[$Key]["R"];
+                }
+                if (isset($Values["BorderG"])) {
+                    $Result[$Key]["BorderG"] = $Values["BorderG"];
+                } else {
+                    $Result[$Key]["BorderG"] = $Result[$Key]["G"];
+                }
+                if (isset($Values["BorderB"])) {
+                    $Result[$Key]["BorderB"] = $Values["BorderB"];
+                } else {
+                    $Result[$Key]["BorderB"] = $Result[$Key]["B"];
+                }
+                if (isset($Values["BorderAlpha"])) {
+                    $Result[$Key]["BorderAlpha"] = $Values["BorderAlpha"];
+                } else {
+                    $Result[$Key]["BorderAlpha"] = $Result[$Key]["Alpha"];
+                }
+            }
+        }
+
+        return $Result;
+    }
+
+    /**
+     * @param mixed $Values
+     * @param array $Option
+     * @param boolean $ReturnOnly0Height
+     * @return int|float|array
+     */
+    public function scaleComputeY($Values, array $Option = [], $ReturnOnly0Height = false)
+    {
+        $AxisID = isset($Option["AxisID"]) ? $Option["AxisID"] : 0;
+        $SerieName = isset($Option["SerieName"]) ? $Option["SerieName"] : null;
+
+        $Data = $this->DataSet->getData();
+        if (!isset($Data["Axis"][$AxisID])) {
+            return -1;
+        }
+
+        if ($SerieName != null) {
+            $AxisID = $Data["Series"][$SerieName]["Axis"];
+        }
+        if (!is_array($Values)) {
+            $tmp = $Values;
+            $Values = [];
+            $Values[0] = $tmp;
+        }
+
+        $Result = [];
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Data["Axis"][$AxisID]["Margin"] * 2;
+            $ScaleHeight = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
+            $Step = $Height / $ScaleHeight;
+
+            if ($ReturnOnly0Height) {
+                foreach ($Values as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Result[] = VOID;
+                    } else {
+                        $Result[] = $Step * $Value;
+                    }
+                }
+            } else {
+                foreach ($Values as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Result[] = VOID;
+                    } else {
+                        $Result[] = $this->GraphAreaY2
+                            - $Data["Axis"][$AxisID]["Margin"]
+                            - ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
+                        ;
+                    }
+                }
+            }
+        } else {
+            $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Data["Axis"][$AxisID]["Margin"] * 2;
+            $ScaleWidth = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
+            $Step = $Width / $ScaleWidth;
+
+            if ($ReturnOnly0Height) {
+                foreach ($Values as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Result[] = VOID;
+                    } else {
+                        $Result[] = $Step * $Value;
+                    }
+                }
+            } else {
+                foreach ($Values as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Result[] = VOID;
+                    } else {
+                        $Result[] = $this->GraphAreaX1
+                            + $Data["Axis"][$AxisID]["Margin"]
+                            + ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
+                        ;
+                    }
+                }
+            }
+        }
+        return count($Result) == 1 ? reset($Result) : $Result;
+    }
+
+    /**
+     * Format the axis values
+     * @param mixed $Value
+     * @param int $Mode
+     * @param array $Format
+     * @param string $Unit
+     * @return string
+     */
+    public function scaleFormat($Value, $Mode = null, $Format = null, $Unit = null)
+    {
+        if ($Value == VOID) {
+            return "";
+        }
+
+        if ($Mode == AXIS_FORMAT_TRAFFIC) {
+            if ($Value == 0) {
+                return "0B";
+            }
+            $Units = ["B", "KB", "MB", "GB", "TB", "PB"];
+            $Sign = "";
+            if ($Value < 0) {
+                $Value = abs($Value);
+                $Sign = "-";
+            }
+
+            $Value = number_format($Value / pow(1024, ($Scale = floor(log($Value, 1024)))), 2, ",", ".");
+            return $Sign . $Value . " " . $Units[$Scale];
+        }
+
+        if ($Mode == AXIS_FORMAT_CUSTOM) {
+            if (is_callable($Format)) {
+                return call_user_func($Format, $Value);
+            }
+        }
+
+        if ($Mode == AXIS_FORMAT_DATE) {
+            $Pattern = "d/m/Y";
+            if ($Format !== null) {
+                $Pattern = $Format;
+            }
+
+            return gmdate($Pattern, $Value);
+        }
+
+        if ($Mode == AXIS_FORMAT_TIME) {
+            $Pattern = "H:i:s";
+            if ($Format !== null) {
+                $Pattern = $Format;
+            }
+
+            return gmdate($Pattern, $Value);
+        }
+
+        if ($Mode == AXIS_FORMAT_CURRENCY) {
+            return $Format . number_format($Value, 2);
+        }
+
+        if ($Mode == AXIS_FORMAT_METRIC) {
+            if (abs($Value) > 1000000000) {
+                return round($Value / 1000000000, $Format) . "g" . $Unit;
+            }
+            if (abs($Value) > 1000000) {
+                return round($Value / 1000000, $Format) . "m" . $Unit;
+            } elseif (abs($Value) >= 1000) {
+                return round($Value / 1000, $Format) . "k" . $Unit;
+            }
+        }
+        return $Value . $Unit;
+    }
+
+    /**
+     * @return array|null
+     */
+    public function scaleGetXSettings()
+    {
+        $Data = $this->DataSet->getData();
+        foreach ($Data["Axis"] as $Settings) {
+            if ($Settings["Identity"] == AXIS_X) {
+                return [$Settings["Margin"], $Settings["Rows"]];
+            }
+        }
+    }
+
+    /**
+     * Write Max value on a chart
+     * @param int $Type
+     * @param array $Format
+     */
+    public function writeBounds($Type = BOUND_BOTH, $Format = null)
+    {
+        $MaxLabelTxt = isset($Format["MaxLabelTxt"]) ? $Format["MaxLabelTxt"] : "max=";
+        $MinLabelTxt = isset($Format["MinLabelTxt"]) ? $Format["MinLabelTxt"] : "min=";
+        $Decimals = isset($Format["Decimals"]) ? $Format["Decimals"] : 1;
+        $ExcludedSeries = isset($Format["ExcludedSeries"]) ? $Format["ExcludedSeries"] : "";
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 4;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $MaxDisplayR = isset($Format["MaxDisplayR"]) ? $Format["MaxDisplayR"] : 0;
+        $MaxDisplayG = isset($Format["MaxDisplayG"]) ? $Format["MaxDisplayG"] : 0;
+        $MaxDisplayB = isset($Format["MaxDisplayB"]) ? $Format["MaxDisplayB"] : 0;
+        $MinDisplayR = isset($Format["MinDisplayR"]) ? $Format["MinDisplayR"] : 255;
+        $MinDisplayG = isset($Format["MinDisplayG"]) ? $Format["MinDisplayG"] : 255;
+        $MinDisplayB = isset($Format["MinDisplayB"]) ? $Format["MinDisplayB"] : 255;
+        $MinLabelPos = isset($Format["MinLabelPos"]) ? $Format["MinLabelPos"] : BOUND_LABEL_POS_AUTO;
+        $MaxLabelPos = isset($Format["MaxLabelPos"]) ? $Format["MaxLabelPos"] : BOUND_LABEL_POS_AUTO;
+        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : true;
+        $DrawBoxBorder = isset($Format["DrawBoxBorder"]) ? $Format["DrawBoxBorder"] : false;
+        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 5;
+        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : true;
+        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 3;
+        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
+        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
+        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 20;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
+        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 255;
+        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 255;
+        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 255;
+        $BoxBorderAlpha = isset($Format["BoxBorderAlpha"]) ? $Format["BoxBorderAlpha"] : 100;
+
+        $CaptionSettings = [
+            "DrawBox" => $DrawBox,
+            "DrawBoxBorder" => $DrawBoxBorder,
+            "BorderOffset" => $BorderOffset,
+            "BoxRounded" => $BoxRounded,
+            "RoundedRadius" => $RoundedRadius,
+            "BoxR" => $BoxR,
+            "BoxG" => $BoxG,
+            "BoxB" => $BoxB,
+            "BoxAlpha" => $BoxAlpha,
+            "BoxSurrounding" => $BoxSurrounding,
+            "BoxBorderR" => $BoxBorderR,
+            "BoxBorderG" => $BoxBorderG,
+            "BoxBorderB" => $BoxBorderB,
+            "BoxBorderAlpha" => $BoxBorderAlpha
+        ];
+
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        $Data = $this->DataSet->getData();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true
+                && $SerieName != $Data["Abscissa"]
+                && !isset($ExcludedSeries[$SerieName])
+            ) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+
+                $MinValue = $this->DataSet->getMin($SerieName);
+                $MaxValue = $this->DataSet->getMax($SerieName);
+
+                $MinPos = VOID;
+                $MaxPos = VOID;
+                foreach ($Serie["Data"] as $Key => $Value) {
+                    if ($Value == $MinValue && $MinPos == VOID) {
+                        $MinPos = $Key;
+                    }
+                    if ($Value == $MaxValue) {
+                        $MaxPos = $Key;
+                    }
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $SerieOffset = isset($Serie["XOffset"]) ? $Serie["XOffset"] : 0;
+
+                    if ($Type == BOUND_MAX || $Type == BOUND_BOTH) {
+                        if ($MaxLabelPos == BOUND_LABEL_POS_TOP
+                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue >= 0)
+                        ) {
+                            $YPos = $PosArray[$MaxPos] - $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                        }
+                        if ($MaxLabelPos == BOUND_LABEL_POS_BOTTOM
+                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue < 0)
+                        ) {
+                            $YPos = $PosArray[$MaxPos] + $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_TOPMIDDLE;
+                        }
+
+                        $XPos = $X + $MaxPos * $XStep + $SerieOffset;
+                        $Label = sprintf(
+                            '%s%s',
+                            $MaxLabelTxt,
+                            $this->scaleFormat(round($MaxValue, $Decimals), $Mode, $Format, $Unit)
+                        );
+
+                        $TxtPos = $this->getTextBox($XPos, $YPos, $this->FontName, $this->FontSize, 0, $Label);
+                        $XOffset = 0;
+                        $YOffset = 0;
+                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
+                            $XOffset = (($this->GraphAreaX1 - $TxtPos[0]["X"]) / 2);
+                        }
+                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
+                            $XOffset = -(($TxtPos[1]["X"] - $this->GraphAreaX2) / 2);
+                        }
+                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
+                            $YOffset = $this->GraphAreaY1 - $TxtPos[2]["Y"];
+                        }
+                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
+                            $YOffset = -($TxtPos[0]["Y"] - $this->GraphAreaY2);
+                        }
+
+                        $CaptionSettings["R"] = $MaxDisplayR;
+                        $CaptionSettings["G"] = $MaxDisplayG;
+                        $CaptionSettings["B"] = $MaxDisplayB;
+                        $CaptionSettings["Align"] = $Align;
+
+                        $this->drawText($XPos + $XOffset, $YPos + $YOffset, $Label, $CaptionSettings);
+                    }
+
+                    if ($Type == BOUND_MIN || $Type == BOUND_BOTH) {
+                        if ($MinLabelPos == BOUND_LABEL_POS_TOP
+                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue >= 0)
+                        ) {
+                            $YPos = $PosArray[$MinPos] - $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                        }
+                        if ($MinLabelPos == BOUND_LABEL_POS_BOTTOM
+                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue < 0)
+                        ) {
+                            $YPos = $PosArray[$MinPos] + $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_TOPMIDDLE;
+                        }
+
+                        $XPos = $X + $MinPos * $XStep + $SerieOffset;
+                        $Label = sprintf(
+                            '%s%s',
+                            $MinLabelTxt,
+                            $this->scaleFormat(round($MinValue, $Decimals), $Mode, $Format, $Unit)
+                        );
+
+                        $TxtPos = $this->getTextBox($XPos, $YPos, $this->FontName, $this->FontSize, 0, $Label);
+                        $XOffset = 0;
+                        $YOffset = 0;
+                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
+                            $XOffset = (($this->GraphAreaX1 - $TxtPos[0]["X"]) / 2);
+                        }
+                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
+                            $XOffset = -(($TxtPos[1]["X"] - $this->GraphAreaX2) / 2);
+                        }
+                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
+                            $YOffset = $this->GraphAreaY1 - $TxtPos[2]["Y"];
+                        }
+                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
+                            $YOffset = -($TxtPos[0]["Y"] - $this->GraphAreaY2);
+                        }
+
+                        $CaptionSettings["R"] = $MinDisplayR;
+                        $CaptionSettings["G"] = $MinDisplayG;
+                        $CaptionSettings["B"] = $MinDisplayB;
+                        $CaptionSettings["Align"] = $Align;
+
+                        $this->drawText(
+                            $XPos + $XOffset,
+                            $YPos - $DisplayOffset + $YOffset,
+                            $Label,
+                            $CaptionSettings
+                        );
+                    }
+                } else {
+                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    $X = $this->GraphAreaY1 + $XMargin;
+                    $SerieOffset = isset($Serie["XOffset"]) ? $Serie["XOffset"] : 0;
+
+                    if ($Type == BOUND_MAX || $Type == BOUND_BOTH) {
+                        if ($MaxLabelPos == BOUND_LABEL_POS_TOP
+                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue >= 0)
+                        ) {
+                            $YPos = $PosArray[$MaxPos] + $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_MIDDLELEFT;
+                        }
+                        if ($MaxLabelPos == BOUND_LABEL_POS_BOTTOM
+                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue < 0)
+                        ) {
+                            $YPos = $PosArray[$MaxPos] - $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_MIDDLERIGHT;
+                        }
+
+                        $XPos = $X + $MaxPos * $XStep + $SerieOffset;
+                        $Label = $MaxLabelTxt . $this->scaleFormat($MaxValue, $Mode, $Format, $Unit);
+
+                        $TxtPos = $this->getTextBox($YPos, $XPos, $this->FontName, $this->FontSize, 0, $Label);
+                        $XOffset = 0;
+                        $YOffset = 0;
+                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
+                            $XOffset = $this->GraphAreaX1 - $TxtPos[0]["X"];
+                        }
+                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
+                            $XOffset = -($TxtPos[1]["X"] - $this->GraphAreaX2);
+                        }
+                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
+                            $YOffset = ($this->GraphAreaY1 - $TxtPos[2]["Y"]) / 2;
+                        }
+                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
+                            $YOffset = -(($TxtPos[0]["Y"] - $this->GraphAreaY2) / 2);
+                        }
+
+                        $CaptionSettings["R"] = $MaxDisplayR;
+                        $CaptionSettings["G"] = $MaxDisplayG;
+                        $CaptionSettings["B"] = $MaxDisplayB;
+                        $CaptionSettings["Align"] = $Align;
+
+                        $this->drawText($YPos + $XOffset, $XPos + $YOffset, $Label, $CaptionSettings);
+                    }
+
+                    if ($Type == BOUND_MIN || $Type == BOUND_BOTH) {
+                        if ($MinLabelPos == BOUND_LABEL_POS_TOP
+                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue >= 0)
+                        ) {
+                            $YPos = $PosArray[$MinPos] + $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_MIDDLELEFT;
+                        }
+                        if ($MinLabelPos == BOUND_LABEL_POS_BOTTOM
+                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue < 0)
+                        ) {
+                            $YPos = $PosArray[$MinPos] - $DisplayOffset + 2;
+                            $Align = TEXT_ALIGN_MIDDLERIGHT;
+                        }
+
+                        $XPos = $X + $MinPos * $XStep + $SerieOffset;
+                        $Label = $MinLabelTxt . $this->scaleFormat($MinValue, $Mode, $Format, $Unit);
+
+                        $TxtPos = $this->getTextBox($YPos, $XPos, $this->FontName, $this->FontSize, 0, $Label);
+                        $XOffset = 0;
+                        $YOffset = 0;
+                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
+                            $XOffset = $this->GraphAreaX1 - $TxtPos[0]["X"];
+                        }
+                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
+                            $XOffset = -($TxtPos[1]["X"] - $this->GraphAreaX2);
+                        }
+                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
+                            $YOffset = ($this->GraphAreaY1 - $TxtPos[2]["Y"]) / 2;
+                        }
+                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
+                            $YOffset = -(($TxtPos[0]["Y"] - $this->GraphAreaY2) / 2);
+                        }
+
+                        $CaptionSettings["R"] = $MinDisplayR;
+                        $CaptionSettings["G"] = $MinDisplayG;
+                        $CaptionSettings["B"] = $MinDisplayB;
+                        $CaptionSettings["Align"] = $Align;
+
+                        $this->drawText($YPos + $XOffset, $XPos + $YOffset, $Label, $CaptionSettings);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Write labels
+     * @param string $SeriesName
+     * @param array $Indexes
+     * @param array $Format
+     */
+    public function writeLabel($SeriesName, $Indexes, array $Format = [])
+    {
+        $OverrideTitle = isset($Format["OverrideTitle"]) ? $Format["OverrideTitle"] : null;
+        $ForceLabels = isset($Format["ForceLabels"]) ? $Format["ForceLabels"] : null;
+        $DrawPoint = isset($Format["DrawPoint"]) ? $Format["DrawPoint"] : LABEL_POINT_BOX;
+        $DrawVerticalLine = isset($Format["DrawVerticalLine"]) ? $Format["DrawVerticalLine"] : false;
+        $VerticalLineR = isset($Format["VerticalLineR"]) ? $Format["VerticalLineR"] : 0;
+        $VerticalLineG = isset($Format["VerticalLineG"]) ? $Format["VerticalLineG"] : 0;
+        $VerticalLineB = isset($Format["VerticalLineB"]) ? $Format["VerticalLineB"] : 0;
+        $VerticalLineAlpha = isset($Format["VerticalLineAlpha"]) ? $Format["VerticalLineAlpha"] : 40;
+        $VerticalLineTicks = isset($Format["VerticalLineTicks"]) ? $Format["VerticalLineTicks"] : 2;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        if (!is_array($Indexes)) {
+            $Index = $Indexes;
+            $Indexes = [];
+            $Indexes[] = $Index;
+        }
+        if (!is_array($SeriesName)) {
+            $SerieName = $SeriesName;
+            $SeriesName = [];
+            $SeriesName[] = $SerieName;
+        }
+        if ($ForceLabels != null && !is_array($ForceLabels)) {
+            $ForceLabel = $ForceLabels;
+            $ForceLabels = [];
+            $ForceLabels[] = $ForceLabel;
+        }
+
+        foreach ($Indexes as $Key => $Index) {
+            $Series = [];
+
+            if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                if ($XDivs == 0) {
+                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                } else {
+                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                }
+                $X = $this->GraphAreaX1 + $XMargin + $Index * $XStep;
+
+                if ($DrawVerticalLine) {
+                    $this->drawLine(
+                        $X,
+                        $this->GraphAreaY1 + $Data["YMargin"],
+                        $X,
+                        $this->GraphAreaY2 - $Data["YMargin"],
+                        [
+                            "R" => $VerticalLineR,
+                            "G" => $VerticalLineG,
+                            "B" => $VerticalLineB,
+                            "Alpha" => $VerticalLineAlpha,
+                            "Ticks" => $VerticalLineTicks
+                        ]
+                    );
+                }
+
+                $MinY = $this->GraphAreaY2;
+                foreach ($SeriesName as $SerieName) {
+                    if (isset($Data["Series"][$SerieName]["Data"][$Index])) {
+                        $AxisID = $Data["Series"][$SerieName]["Axis"];
+                        $XAxisMode = $Data["XAxisDisplay"];
+                        $XAxisFormat = $Data["XAxisFormat"];
+                        $XAxisUnit = $Data["XAxisUnit"];
+                        $AxisMode = $Data["Axis"][$AxisID]["Display"];
+                        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
+                        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
+                        $XLabel = "";
+
+                        if (isset($Data["Abscissa"])
+                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
+                        ) {
+                            $XLabel = $this->scaleFormat(
+                                $Data["Series"][$Data["Abscissa"]]["Data"][$Index],
+                                $XAxisMode,
+                                $XAxisFormat,
+                                $XAxisUnit
+                            );
+                        }
+
+                        if ($OverrideTitle != null) {
+                            $Description = $OverrideTitle;
+                        } elseif (count($SeriesName) == 1) {
+                            $Description = $Data["Series"][$SerieName]["Description"] . " - " . $XLabel;
+                        } elseif (isset($Data["Abscissa"])
+                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
+                        ) {
+                            $Description = $XLabel;
+                        }
+
+                        $Serie = [
+                            "R" => $Data["Series"][$SerieName]["Color"]["R"],
+                            "G" => $Data["Series"][$SerieName]["Color"]["G"],
+                            "B" => $Data["Series"][$SerieName]["Color"]["B"],
+                            "Alpha" => $Data["Series"][$SerieName]["Color"]["Alpha"]
+                        ];
+                        if (count($SeriesName) == 1
+                            && isset($Data["Series"][$SerieName]["XOffset"])
+                        ) {
+                            $SerieOffset = $Data["Series"][$SerieName]["XOffset"];
+                        } else {
+                            $SerieOffset = 0;
+                        }
+                        $Value = $Data["Series"][$SerieName]["Data"][$Index];
+                        if ($Value == VOID) {
+                            $Value = "NaN";
+                        }
+
+                        if ($ForceLabels != null) {
+                            $Caption = isset($ForceLabels[$Key]) ? $ForceLabels[$Key] : "Not set";
+                        } else {
+                            $Caption = $this->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
+                        }
+
+                        if ($this->LastChartLayout == CHART_LAST_LAYOUT_STACKED) {
+                            if ($Value >= 0) {
+                                $LookFor = "+";
+                            } else {
+                                $LookFor = "-";
+                            }
+
+                            $Value = 0;
+                            $Done = false;
+                            foreach ($Data["Series"] as $Name => $SerieLookup) {
+                                if ($SerieLookup["isDrawable"] == true
+                                    && $Name != $Data["Abscissa"] && !$Done
+                                ) {
+                                    if (isset($Data["Series"][$Name]["Data"][$Index])
+                                        && $Data["Series"][$Name]["Data"][$Index] != VOID
+                                    ) {
+                                        if ($Data["Series"][$Name]["Data"][$Index] >= 0 && $LookFor == "+") {
+                                            $Value = $Value + $Data["Series"][$Name]["Data"][$Index];
+                                        }
+                                        if ($Data["Series"][$Name]["Data"][$Index] < 0 && $LookFor == "-") {
+                                            $Value = $Value - $Data["Series"][$Name]["Data"][$Index];
+                                        }
+                                        if ($Name == $SerieName) {
+                                            $Done = true;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        $X = floor($this->GraphAreaX1 + $XMargin + $Index * $XStep + $SerieOffset);
+                        $Y = floor($this->scaleComputeY($Value, ["AxisID" => $AxisID]));
+
+                        if ($Y < $MinY) {
+                            $MinY = $Y;
+                        }
+
+                        if ($DrawPoint == LABEL_POINT_CIRCLE) {
+                            $this->drawFilledCircle(
+                                $X,
+                                $Y,
+                                3,
+                                [
+                                    "R" => 255,
+                                    "G" => 255,
+                                    "B" => 255,
+                                    "BorderR" => 0,
+                                    "BorderG" => 0,
+                                    "BorderB" => 0
+                                ]
+                            );
+                        } elseif ($DrawPoint == LABEL_POINT_BOX) {
+                            $this->drawFilledRectangle(
+                                $X - 2,
+                                $Y - 2,
+                                $X + 2,
+                                $Y + 2,
+                                [
+                                    "R" => 255,
+                                    "G" => 255,
+                                    "B" => 255,
+                                    "BorderR" => 0,
+                                    "BorderG" => 0,
+                                    "BorderB" => 0
+                                ]
+                            );
+                        }
+                        $Series[] = ["Format" => $Serie, "Caption" => $Caption];
+                    }
+                }
+                $this->drawLabelBox($X, $MinY - 3, $Description, $Series, $Format);
+            } else {
+                if ($XDivs == 0) {
+                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                } else {
+                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                }
+                $Y = $this->GraphAreaY1 + $XMargin + $Index * $XStep;
+
+                if ($DrawVerticalLine) {
+                    $this->drawLine(
+                        $this->GraphAreaX1 + $Data["YMargin"],
+                        $Y,
+                        $this->GraphAreaX2 - $Data["YMargin"],
+                        $Y,
+                        [
+                            "R" => $VerticalLineR,
+                            "G" => $VerticalLineG,
+                            "B" => $VerticalLineB,
+                            "Alpha" => $VerticalLineAlpha,
+                            "Ticks" => $VerticalLineTicks
+                        ]
+                    );
+                }
+
+                $MinX = $this->GraphAreaX2;
+                foreach ($SeriesName as $Key => $SerieName) {
+                    if (isset($Data["Series"][$SerieName]["Data"][$Index])) {
+                        $AxisID = $Data["Series"][$SerieName]["Axis"];
+                        $XAxisMode = $Data["XAxisDisplay"];
+                        $XAxisFormat = $Data["XAxisFormat"];
+                        $XAxisUnit = $Data["XAxisUnit"];
+                        $AxisMode = $Data["Axis"][$AxisID]["Display"];
+                        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
+                        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
+                        $XLabel = "";
+
+                        if (isset($Data["Abscissa"])
+                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
+                        ) {
+                            $XLabel = $this->scaleFormat(
+                                $Data["Series"][$Data["Abscissa"]]["Data"][$Index],
+                                $XAxisMode,
+                                $XAxisFormat,
+                                $XAxisUnit
+                            );
+                        }
+
+                        if ($OverrideTitle != null) {
+                            $Description = $OverrideTitle;
+                        } elseif (count($SeriesName) == 1) {
+                            if (isset($Data["Abscissa"])
+                                && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
+                            ) {
+                                $Description = $Data["Series"][$SerieName]["Description"] . " - " . $XLabel;
+                            }
+                        } elseif (isset($Data["Abscissa"])
+                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
+                        ) {
+                            $Description = $XLabel;
+                        }
+                        $Serie = [];
+                        if (isset($Data["Extended"]["Palette"][$Index])) {
+                            $Serie["R"] = $Data["Extended"]["Palette"][$Index]["R"];
+                            $Serie["G"] = $Data["Extended"]["Palette"][$Index]["G"];
+                            $Serie["B"] = $Data["Extended"]["Palette"][$Index]["B"];
+                            $Serie["Alpha"] = $Data["Extended"]["Palette"][$Index]["Alpha"];
+                        } else {
+                            $Serie["R"] = $Data["Series"][$SerieName]["Color"]["R"];
+                            $Serie["G"] = $Data["Series"][$SerieName]["Color"]["G"];
+                            $Serie["B"] = $Data["Series"][$SerieName]["Color"]["B"];
+                            $Serie["Alpha"] = $Data["Series"][$SerieName]["Color"]["Alpha"];
+                        }
+
+                        if (count($SeriesName) == 1 && isset($Data["Series"][$SerieName]["XOffset"])) {
+                            $SerieOffset = $Data["Series"][$SerieName]["XOffset"];
+                        } else {
+                            $SerieOffset = 0;
+                        }
+
+                        $Value = $Data["Series"][$SerieName]["Data"][$Index];
+                        if ($ForceLabels != null) {
+                            $Caption = isset($ForceLabels[$Key]) ? $ForceLabels[$Key] : "Not set";
+                        } else {
+                            $Caption = $this->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
+                        }
+                        if ($Value == VOID) {
+                            $Value = "NaN";
+                        }
+
+                        if ($this->LastChartLayout == CHART_LAST_LAYOUT_STACKED) {
+                            if ($Value >= 0) {
+                                $LookFor = "+";
+                            } else {
+                                $LookFor = "-";
+                            }
+
+                            $Value = 0;
+                            $Done = false;
+                            foreach ($Data["Series"] as $Name => $SerieLookup) {
+                                if ($SerieLookup["isDrawable"] == true
+                                    && $Name != $Data["Abscissa"]
+                                    && !$Done
+                                ) {
+                                    if (isset($Data["Series"][$Name]["Data"][$Index])
+                                        && $Data["Series"][$Name]["Data"][$Index] != VOID
+                                    ) {
+                                        if ($Data["Series"][$Name]["Data"][$Index] >= 0 && $LookFor == "+") {
+                                            $Value = $Value + $Data["Series"][$Name]["Data"][$Index];
+                                        }
+                                        if ($Data["Series"][$Name]["Data"][$Index] < 0 && $LookFor == "-") {
+                                            $Value = $Value - $Data["Series"][$Name]["Data"][$Index];
+                                        }
+                                        if ($Name == $SerieName) {
+                                            $Done = true;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        $X = floor($this->scaleComputeY($Value, ["AxisID" => $AxisID]));
+                        $Y = floor($this->GraphAreaY1 + $XMargin + $Index * $XStep + $SerieOffset);
+
+                        if ($X < $MinX) {
+                            $MinX = $X;
+                        }
+
+                        if ($DrawPoint == LABEL_POINT_CIRCLE) {
+                            $this->drawFilledCircle(
+                                $X,
+                                $Y,
+                                3,
+                                [
+                                    "R" => 255,
+                                    "G" => 255,
+                                    "B" => 255,
+                                    "BorderR" => 0,
+                                    "BorderG" => 0,
+                                    "BorderB" => 0
+                                ]
+                            );
+                        } elseif ($DrawPoint == LABEL_POINT_BOX) {
+                            $this->drawFilledRectangle(
+                                $X - 2,
+                                $Y - 2,
+                                $X + 2,
+                                $Y + 2,
+                                [
+                                    "R" => 255,
+                                    "G" => 255,
+                                    "B" => 255,
+                                    "BorderR" => 0,
+                                    "BorderG" => 0,
+                                    "BorderB" => 0
+                                ]
+                            );
+                        }
+                        $Series[] = ["Format" => $Serie, "Caption" => $Caption];
+                    }
+                }
+                $this->drawLabelBox($MinX, $Y - 3, $Description, $Series, $Format);
+            }
+        }
+    }
+
+    /**
+     * @param GdImage|resource $image
+     * @param array $points
+     * @param int $numPoints
+     * @param int $color
+     * @return void
+     */
+    protected function imageFilledPolygonWrapper(
+        $image,
+        array $points,
+        $numPoints,
+        $color
+    ) {
+        if (version_compare(PHP_VERSION, '8.1.0') === -1) {
+            imagefilledpolygon($image, $points, $numPoints, $color);
+        } else {
+            imagefilledpolygon($image, $points, $color);
+        }
+    }
+}

+ 392 - 0
vendor/szymach/c-pchart/src/Cache.php

@@ -0,0 +1,392 @@
+<?php
+
+namespace CpChart;
+
+/**
+ * Cache - speed up the rendering by caching up the pictures
+ *
+ * Version     : 2.1.4
+ * Made by     : Jean-Damien POGOLOTTI
+ * Last Update : 19/01/2014
+ *
+ * This file can be distributed under the license you can find at :
+ *
+ *                http://www.pchart.net/license
+ *
+ * You can find the whole class documentation on the pChart web site.
+ */
+class Cache
+{
+    /**
+     * @var string
+     */
+    public $CacheFolder;
+
+    /**
+     * @var string
+     */
+    public $CacheIndex;
+
+    /**
+     * @var string
+     */
+    public $CacheDB;
+
+    /**
+     * @param array $Settings
+     */
+    public function __construct(array $Settings = [])
+    {
+        $CacheFolder = isset($Settings["CacheFolder"]) ? $Settings["CacheFolder"] : __DIR__ . "/../cache";
+        $CacheIndex = isset($Settings["CacheIndex"]) ? $Settings["CacheIndex"] : "index.db";
+        $CacheDB = isset($Settings["CacheDB"]) ? $Settings["CacheDB"] : "cache.db";
+
+        $this->CacheFolder = $CacheFolder;
+        $this->CacheIndex = $CacheIndex;
+        $this->CacheDB = $CacheDB;
+        if (!file_exists($this->CacheFolder . "/" . $this->CacheIndex)) {
+            touch($this->CacheFolder . "/" . $this->CacheIndex);
+        }
+        if (!file_exists($this->CacheFolder . "/" . $this->CacheDB)) {
+            touch($this->CacheFolder . "/" . $this->CacheDB);
+        }
+    }
+
+    /**
+     * Flush the cache contents
+     */
+    public function flush()
+    {
+        if (file_exists($this->CacheFolder . "/" . $this->CacheIndex)) {
+            unlink($this->CacheFolder . "/" . $this->CacheIndex);
+            touch($this->CacheFolder . "/" . $this->CacheIndex);
+        }
+        if (file_exists($this->CacheFolder . "/" . $this->CacheDB)) {
+            unlink($this->CacheFolder . "/" . $this->CacheDB);
+            touch($this->CacheFolder . "/" . $this->CacheDB);
+        }
+    }
+
+    /**
+     * Return the MD5 of the data array to clearly identify the chart
+     *
+     * @param Data $Data
+     * @param string $Marker
+     * @return string
+     */
+    public function getHash(Data $Data, $Marker = "")
+    {
+        return md5($Marker . serialize($Data->Data));
+    }
+
+    /**
+     * Write the generated picture to the cache
+     *
+     * @param string $ID
+     * @param Image $pChartObject
+     */
+    public function writeToCache($ID, Image $pChartObject)
+    {
+        /* Compute the paths */
+        $TemporaryFile = tempnam($this->CacheFolder, "tmp_");
+        $Database = $this->CacheFolder . "/" . $this->CacheDB;
+        $Index = $this->CacheFolder . "/" . $this->CacheIndex;
+        /* Flush the picture to a temporary file */
+        imagepng($pChartObject->Picture, $TemporaryFile);
+
+        /* Retrieve the files size */
+        $PictureSize = filesize($TemporaryFile);
+        $DBSize = filesize($Database);
+
+        /* Save the index */
+        $Handle = fopen($Index, "a");
+        fwrite($Handle, $ID . "," . $DBSize . "," . $PictureSize . "," . time() . ",0\r\n");
+        fclose($Handle);
+
+        /* Get the picture raw contents */
+        $Handle = fopen($TemporaryFile, "r");
+        $Raw = fread($Handle, $PictureSize);
+        fclose($Handle);
+
+        /* Save the picture in the solid database file */
+        $Handle = fopen($Database, "a");
+        fwrite($Handle, $Raw);
+        fclose($Handle);
+
+        /* Remove temporary file */
+        unlink($TemporaryFile);
+    }
+
+    /**
+     * Remove object older than the specified TS
+     * @param int $Expiry
+     */
+    public function removeOlderThan($Expiry)
+    {
+        $this->dbRemoval(["Expiry" => $Expiry]);
+    }
+
+    /**
+     * Remove an object from the cache
+     * @param string $ID
+     */
+    public function remove($ID)
+    {
+        $this->dbRemoval(["Name" => $ID]);
+    }
+
+    /**
+     * Remove with specified criterias.
+     *
+     * @param array $Settings
+     * @return int
+     */
+    public function dbRemoval(array $Settings)
+    {
+        $ID = isset($Settings["Name"]) ? $Settings["Name"] : null;
+        $Expiry = isset($Settings["Expiry"]) ? $Settings["Expiry"] : -(24 * 60 * 60);
+        $TS = time() - $Expiry;
+
+        /* Compute the paths */
+        $Database = $this->CacheFolder . "/" . $this->CacheDB;
+        $Index = $this->CacheFolder . "/" . $this->CacheIndex;
+        $DatabaseTemp = $this->CacheFolder . "/" . $this->CacheDB . ".tmp";
+        $IndexTemp = $this->CacheFolder . "/" . $this->CacheIndex . ".tmp";
+
+        /* Single file removal */
+        if ($ID != null) {
+            /* Retrieve object informations */
+            $Object = $this->isInCache($ID, true);
+
+            /* If it's not in the cache DB, go away */
+            if (!$Object) {
+                return(0);
+            }
+        }
+
+        /* Create the temporary files */
+        if (!file_exists($DatabaseTemp)) {
+            touch($DatabaseTemp);
+        }
+        if (!file_exists($IndexTemp)) {
+            touch($IndexTemp);
+        }
+
+        /* Open the file handles */
+        $IndexHandle = @fopen($Index, "r");
+        $IndexTempHandle = @fopen($IndexTemp, "w");
+        $DBHandle = @fopen($Database, "r");
+        $DBTempHandle = @fopen($DatabaseTemp, "w");
+
+        /* Remove the selected ID from the database */
+        while (!feof($IndexHandle)) {
+            $Entry = fgets($IndexHandle, 4096);
+            $Entry = str_replace("\r", "", $Entry);
+            $Entry = str_replace("\n", "", $Entry);
+            $Settings = preg_split("/,/", $Entry);
+
+            if ($Entry != "") {
+                $PicID = $Settings[0];
+                $DBPos = $Settings[1];
+                $PicSize = $Settings[2];
+                $GeneratedTS = $Settings[3];
+                $Hits = $Settings[4];
+
+                if ($Settings[0] != $ID && $GeneratedTS > $TS) {
+                    $CurrentPos = ftell($DBTempHandle);
+                    fwrite(
+                        $IndexTempHandle,
+                        sprintf(
+                            "%s,%s,%s,%s,%s\r\n",
+                            $PicID,
+                            $CurrentPos,
+                            $PicSize,
+                            $GeneratedTS,
+                            $Hits
+                        )
+                    );
+
+                    fseek($DBHandle, $DBPos);
+                    $Picture = fread($DBHandle, $PicSize);
+                    fwrite($DBTempHandle, $Picture);
+                }
+            }
+        }
+
+        /* Close the handles */
+        fclose($IndexHandle);
+        fclose($IndexTempHandle);
+        fclose($DBHandle);
+        fclose($DBTempHandle);
+
+        /* Remove the prod files */
+        unlink($Database);
+        unlink($Index);
+
+        /* Swap the temp & prod DB */
+        rename($DatabaseTemp, $Database);
+        rename($IndexTemp, $Index);
+    }
+
+    /**
+     * Is the file in cache?
+     *
+     * @param string $ID
+     * @param boolean $Verbose
+     * @param boolean $UpdateHitsCount
+     * @return boolean
+     */
+    public function isInCache($ID, $Verbose = false, $UpdateHitsCount = false)
+    {
+        /* Compute the paths */
+        $Index = $this->CacheFolder . "/" . $this->CacheIndex;
+
+        /* Search the picture in the index file */
+        $Handle = @fopen($Index, "r");
+        while (!feof($Handle)) {
+            $IndexPos = ftell($Handle);
+            $Entry = fgets($Handle, 4096);
+            if ($Entry != "") {
+                $Settings = preg_split("/,/", $Entry);
+                $PicID = $Settings[0];
+                if ($PicID == $ID) {
+                    fclose($Handle);
+
+                    $DBPos = $Settings[1];
+                    $PicSize = $Settings[2];
+                    $GeneratedTS = $Settings[3];
+                    $Hits = intval($Settings[4]);
+
+                    if ($UpdateHitsCount) {
+                        $Hits++;
+                        if (strlen($Hits) < 7) {
+                            $Hits = $Hits . str_repeat(" ", 7 - strlen($Hits));
+                        }
+
+                        $Handle = @fopen($Index, "r+");
+                        fseek($Handle, $IndexPos);
+                        fwrite(
+                            $Handle,
+                            sprintf(
+                                "%s,%s,%s,%s,%s\r\n",
+                                $PicID,
+                                $DBPos,
+                                $PicSize,
+                                $GeneratedTS,
+                                $Hits
+                            )
+                        );
+                        fclose($Handle);
+                    }
+
+                    if ($Verbose) {
+                        return [
+                            "DBPos" => $DBPos,
+                            "PicSize" => $PicSize,
+                            "GeneratedTS" => $GeneratedTS,
+                            "Hits" => $Hits
+                        ];
+                    } else {
+                        return true;
+                    }
+                }
+            }
+        }
+        fclose($Handle);
+
+        /* Picture isn't in the cache */
+        return false;
+    }
+
+    /**
+     * Automatic output method based on the calling interface
+     * @param string $ID
+     * @param string $Destination
+     */
+    public function autoOutput($ID, $Destination = "output.png")
+    {
+        if (php_sapi_name() == "cli") {
+            $this->saveFromCache($ID, $Destination);
+        } else {
+            $this->strokeFromCache($ID);
+        }
+    }
+
+    /**
+     * Show image from cache
+     * @param string $ID
+     * @return boolean
+     */
+    public function strokeFromCache($ID)
+    {
+        /* Get the raw picture from the cache */
+        $Picture = $this->getFromCache($ID);
+
+        /* Do we have a hit? */
+        if ($Picture == null) {
+            return false;
+        }
+
+        header('Content-type: image/png');
+        echo $Picture;
+
+        return true;
+    }
+
+    /**
+     * Save file from cache.
+     * @param string $ID
+     * @param string $Destination
+     * @return boolean
+     */
+    public function saveFromCache($ID, $Destination)
+    {
+        /* Get the raw picture from the cache */
+        $Picture = $this->getFromCache($ID);
+
+        /* Do we have a hit? */
+        if ($Picture == null) {
+            return false;
+        }
+
+        /* Flush the picture to a file */
+        $Handle = fopen($Destination, "w");
+        fwrite($Handle, $Picture);
+        fclose($Handle);
+
+        /* All went fine */
+        return true;
+    }
+
+    /**
+     * Get file from cache
+     * @param string $ID
+     * @return string|null
+     */
+    public function getFromCache($ID)
+    {
+        /* Compute the path */
+        $Database = $this->CacheFolder . "/" . $this->CacheDB;
+
+        /* Lookup for the picture in the cache */
+        $CacheInfo = $this->isInCache($ID, true, true);
+
+        /* Not in the cache */
+        if (!$CacheInfo) {
+            return null;
+        }
+
+        /* Get the database extended information */
+        $DBPos = $CacheInfo["DBPos"];
+        $PicSize = $CacheInfo["PicSize"];
+
+        /* Extract the picture from the solid cache file */
+        $Handle = @fopen($Database, "r");
+        fseek($Handle, $DBPos);
+        $Picture = fread($Handle, $PicSize);
+        fclose($Handle);
+
+        /* Return back the raw picture data */
+        return $Picture;
+    }
+}

+ 532 - 0
vendor/szymach/c-pchart/src/Chart/Bubble.php

@@ -0,0 +1,532 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+
+/**
+ *  Bubble - class to draw bubble charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Bubble
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var Data
+     */
+    public $pDataObject;
+
+    /**
+     * @param Image $pChartObject
+     * @param Data $pDataObject
+     */
+    public function __construct(Image $pChartObject, Data $pDataObject)
+    {
+        $this->pChartObject = $pChartObject;
+        $this->pDataObject = $pDataObject;
+    }
+
+    /**
+     * Prepare the scale
+     *
+     * @param mixed $DataSeries
+     * @param mixed $WeightSeries
+     */
+    public function bubbleScale($DataSeries, $WeightSeries)
+    {
+        if (!is_array($DataSeries)) {
+            $DataSeries = [$DataSeries];
+        }
+        if (!is_array($WeightSeries)) {
+            $WeightSeries = [$WeightSeries];
+        }
+
+        /* Parse each data series to find the new min & max boundaries to scale */
+        $NewPositiveSerie = [];
+        $NewNegativeSerie = [];
+        $MaxValues = 0;
+        $LastPositive = 0;
+        $LastNegative = 0;
+
+        foreach ($DataSeries as $Key => $SerieName) {
+            $SerieWeightName = $WeightSeries[$Key];
+
+            $this->pDataObject->setSerieDrawable($SerieWeightName, false);
+            $serieData = $this->pDataObject->Data["Series"][$SerieName]["Data"];
+            if (count($serieData) > $MaxValues) {
+                $MaxValues = count($serieData);
+            }
+
+            foreach ($serieData as $Key => $Value) {
+                if ($Value >= 0) {
+                    $BubbleBounds = $Value + $this->pDataObject->Data["Series"][$SerieWeightName]["Data"][$Key];
+
+                    if (!isset($NewPositiveSerie[$Key])) {
+                        $NewPositiveSerie[$Key] = $BubbleBounds;
+                    } elseif ($NewPositiveSerie[$Key] < $BubbleBounds) {
+                        $NewPositiveSerie[$Key] = $BubbleBounds;
+                    }
+                    $LastPositive = $BubbleBounds;
+                } else {
+                    $BubbleBounds = $Value - $this->pDataObject->Data["Series"][$SerieWeightName]["Data"][$Key];
+
+                    if (!isset($NewNegativeSerie[$Key])) {
+                        $NewNegativeSerie[$Key] = $BubbleBounds;
+                    } elseif ($NewNegativeSerie[$Key] > $BubbleBounds) {
+                        $NewNegativeSerie[$Key] = $BubbleBounds;
+                    }
+                    $LastNegative = $BubbleBounds;
+                }
+            }
+        }
+
+        /* Check for missing values and all the fake positive serie */
+        if (count($NewPositiveSerie)) {
+            for ($i = 0; $i < $MaxValues; $i++) {
+                if (!isset($NewPositiveSerie[$i])) {
+                    $NewPositiveSerie[$i] = $LastPositive;
+                }
+            }
+            $this->pDataObject->addPoints($NewPositiveSerie, "BubbleFakePositiveSerie");
+        }
+
+        /* Check for missing values and all the fake negative serie */
+        if (count($NewNegativeSerie)) {
+            for ($i = 0; $i < $MaxValues; $i++) {
+                if (!isset($NewNegativeSerie[$i])) {
+                    $NewNegativeSerie[$i] = $LastNegative;
+                }
+            }
+
+            $this->pDataObject->addPoints($NewNegativeSerie, "BubbleFakeNegativeSerie");
+        }
+    }
+
+    public function resetSeriesColors()
+    {
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        $ID = 0;
+        foreach ($Data["Series"] as $SerieName => $SeriesParameters) {
+            if ($SeriesParameters["isDrawable"]) {
+                $this->pDataObject->Data["Series"][$SerieName]["Color"]["R"] = $Palette[$ID]["R"];
+                $this->pDataObject->Data["Series"][$SerieName]["Color"]["G"] = $Palette[$ID]["G"];
+                $this->pDataObject->Data["Series"][$SerieName]["Color"]["B"] = $Palette[$ID]["B"];
+                $this->pDataObject->Data["Series"][$SerieName]["Color"]["Alpha"] = $Palette[$ID]["Alpha"];
+                $ID++;
+            }
+        }
+    }
+
+    /* Prepare the scale */
+
+    public function drawBubbleChart($DataSeries, $WeightSeries, $Format = "")
+    {
+        $ForceAlpha = isset($Format["ForceAlpha"]) ? $Format["ForceAlpha"] : VOID;
+        $DrawBorder = isset($Format["DrawBorder"]) ? $Format["DrawBorder"] : true;
+        $BorderWidth = isset($Format["BorderWidth"]) ? $Format["BorderWidth"] : 1;
+        $Shape = isset($Format["Shape"]) ? $Format["Shape"] : BUBBLE_SHAPE_ROUND;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 0;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 0;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 0;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 30;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        if (!is_array($DataSeries)) {
+            $DataSeries = [$DataSeries];
+        }
+        if (!is_array($WeightSeries)) {
+            $WeightSeries = [$WeightSeries];
+        }
+
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        if (isset($Data["Series"]["BubbleFakePositiveSerie"])) {
+            $this->pDataObject->setSerieDrawable("BubbleFakePositiveSerie", false);
+        }
+        if (isset($Data["Series"]["BubbleFakeNegativeSerie"])) {
+            $this->pDataObject->setSerieDrawable("BubbleFakeNegativeSerie", false);
+        }
+
+        $this->resetSeriesColors();
+
+        list($XMargin, $XDivs) = $this->pChartObject->scaleGetXSettings();
+
+        foreach ($DataSeries as $Key => $SerieName) {
+            $AxisID = $Data["Series"][$SerieName]["Axis"];
+            $Mode = $Data["Axis"][$AxisID]["Display"];
+            $Format = $Data["Axis"][$AxisID]["Format"];
+            $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+            if (isset($Data["Series"][$SerieName]["Description"])) {
+                $SerieDescription = $Data["Series"][$SerieName]["Description"];
+            } else {
+                $SerieDescription = $SerieName;
+            }
+
+            $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2) / $XDivs;
+
+            $X = $this->pChartObject->GraphAreaX1 + $XMargin;
+            $Y = $this->pChartObject->GraphAreaY1 + $XMargin;
+
+            $Color = [
+                "R" => $Palette[$Key]["R"],
+                "G" => $Palette[$Key]["G"],
+                "B" => $Palette[$Key]["B"],
+                "Alpha" => $Palette[$Key]["Alpha"]
+            ];
+
+            if ($ForceAlpha != VOID) {
+                $Color["Alpha"] = $ForceAlpha;
+            }
+
+            if ($DrawBorder) {
+                if ($BorderWidth != 1) {
+                    if ($Surrounding != null) {
+                        $BorderR = $Palette[$Key]["R"] + $Surrounding;
+                        $BorderG = $Palette[$Key]["G"] + $Surrounding;
+                        $BorderB = $Palette[$Key]["B"] + $Surrounding;
+                    }
+                    if ($ForceAlpha != VOID) {
+                        $BorderAlpha = $ForceAlpha / 2;
+                    }
+                    $BorderColor = [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha
+                    ];
+                } else {
+                    $Color["BorderAlpha"] = $BorderAlpha;
+
+                    if ($Surrounding != null) {
+                        $Color["BorderR"] = $Palette[$Key]["R"] + $Surrounding;
+                        $Color["BorderG"] = $Palette[$Key]["G"] + $Surrounding;
+                        $Color["BorderB"] = $Palette[$Key]["B"] + $Surrounding;
+                    } else {
+                        $Color["BorderR"] = $BorderR;
+                        $Color["BorderG"] = $BorderG;
+                        $Color["BorderB"] = $BorderB;
+                    }
+                    if ($ForceAlpha != VOID) {
+                        $Color["BorderAlpha"] = $ForceAlpha / 2;
+                    }
+                }
+            }
+
+            foreach ($Data["Series"][$SerieName]["Data"] as $iKey => $Point) {
+                $Weight = $Point + $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey];
+
+                $PosArray = $this->pChartObject->scaleComputeY(
+                    $Point,
+                    ["AxisID" => $AxisID]
+                );
+                $WeightArray = $this->pChartObject->scaleComputeY(
+                    $Weight,
+                    ["AxisID" => $AxisID]
+                );
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = 0;
+                    } else {
+                        $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2)
+                            / $XDivs
+                        ;
+                    }
+                    $Y = floor($PosArray);
+                    $CircleRadius = floor(abs($PosArray - $WeightArray) / 2);
+
+                    if ($Shape == BUBBLE_SHAPE_SQUARE) {
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "RECT",
+                                (
+                                    floor($X - $CircleRadius)
+                                    . "," . floor($Y - $CircleRadius)
+                                    . "," . floor($X + $CircleRadius)
+                                    . "," . floor($Y + $CircleRadius)
+                                ),
+                                $this->pChartObject->toHTMLColor(
+                                    $Palette[$Key]["R"],
+                                    $Palette[$Key]["G"],
+                                    $Palette[$Key]["B"]
+                                ),
+                                $SerieDescription,
+                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
+                            );
+                        }
+
+                        if ($BorderWidth != 1) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius - $BorderWidth,
+                                $Y - $CircleRadius - $BorderWidth,
+                                $X + $CircleRadius + $BorderWidth,
+                                $Y + $CircleRadius + $BorderWidth,
+                                $BorderColor
+                            );
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius,
+                                $Y - $CircleRadius,
+                                $X + $CircleRadius,
+                                $Y + $CircleRadius,
+                                $Color
+                            );
+                        } else {
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius,
+                                $Y - $CircleRadius,
+                                $X + $CircleRadius,
+                                $Y + $CircleRadius,
+                                $Color
+                            );
+                        }
+                    } elseif ($Shape == BUBBLE_SHAPE_ROUND) {
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . floor($CircleRadius),
+                                $this->pChartObject->toHTMLColor(
+                                    $Palette[$Key]["R"],
+                                    $Palette[$Key]["G"],
+                                    $Palette[$Key]["B"]
+                                ),
+                                $SerieDescription,
+                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
+                            );
+                        }
+
+                        if ($BorderWidth != 1) {
+                            $this->pChartObject->drawFilledCircle(
+                                $X,
+                                $Y,
+                                $CircleRadius + $BorderWidth,
+                                $BorderColor
+                            );
+                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
+                        } else {
+                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
+                        }
+                    }
+
+                    $X = $X + $XStep;
+                } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+                    if ($XDivs == 0) {
+                        $XStep = 0;
+                    } else {
+                        $XStep = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $XMargin * 2)
+                            / $XDivs
+                        ;
+                    }
+                    $X = floor($PosArray);
+                    $CircleRadius = floor(abs($PosArray - $WeightArray) / 2);
+
+                    if ($Shape == BUBBLE_SHAPE_SQUARE) {
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "RECT",
+                                (
+                                    floor($X - $CircleRadius) . ","
+                                    . floor($Y - $CircleRadius) . ","
+                                    . floor($X + $CircleRadius) . ","
+                                    . floor($Y + $CircleRadius)
+                                ),
+                                $this->pChartObject->toHTMLColor(
+                                    $Palette[$Key]["R"],
+                                    $Palette[$Key]["G"],
+                                    $Palette[$Key]["B"]
+                                ),
+                                $SerieDescription,
+                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
+                            );
+                        }
+
+                        if ($BorderWidth != 1) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius - $BorderWidth,
+                                $Y - $CircleRadius - $BorderWidth,
+                                $X + $CircleRadius + $BorderWidth,
+                                $Y + $CircleRadius + $BorderWidth,
+                                $BorderColor
+                            );
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius,
+                                $Y - $CircleRadius,
+                                $X + $CircleRadius,
+                                $Y + $CircleRadius,
+                                $Color
+                            );
+                        } else {
+                            $this->pChartObject->drawFilledRectangle(
+                                $X - $CircleRadius,
+                                $Y - $CircleRadius,
+                                $X + $CircleRadius,
+                                $Y + $CircleRadius,
+                                $Color
+                            );
+                        }
+                    } elseif ($Shape == BUBBLE_SHAPE_ROUND) {
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . floor($CircleRadius),
+                                $this->pChartObject->toHTMLColor(
+                                    $Palette[$Key]["R"],
+                                    $Palette[$Key]["G"],
+                                    $Palette[$Key]["B"]
+                                ),
+                                $SerieDescription,
+                                $Data["Series"][$WeightSeries[$Key]]["Data"][$iKey]
+                            );
+                        }
+
+                        if ($BorderWidth != 1) {
+                            $this->pChartObject->drawFilledCircle(
+                                $X,
+                                $Y,
+                                $CircleRadius + $BorderWidth,
+                                $BorderColor
+                            );
+                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
+                        } else {
+                            $this->pChartObject->drawFilledCircle($X, $Y, $CircleRadius, $Color);
+                        }
+                    }
+
+                    $Y = $Y + $XStep;
+                }
+            }
+        }
+    }
+
+    public function writeBubbleLabel($SerieName, $SerieWeightName, $Points, $Format = "")
+    {
+        $DrawPoint = isset($Format["DrawPoint"]) ? $Format["DrawPoint"] : LABEL_POINT_BOX;
+
+        if (!is_array($Points)) {
+            $Point = $Points;
+            $Points = [];
+            $Points[] = $Point;
+        }
+
+        $Data = $this->pDataObject->getData();
+
+        if (!isset($Data["Series"][$SerieName])
+            || !isset($Data["Series"][$SerieWeightName])
+        ) {
+            return(0);
+        }
+        list($XMargin, $XDivs) = $this->pChartObject->scaleGetXSettings();
+
+        $AxisID = $Data["Series"][$SerieName]["Axis"];
+        $AxisMode = $Data["Axis"][$AxisID]["Display"];
+        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
+        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
+        $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2) / $XDivs;
+
+        $X = $InitialX = $this->pChartObject->GraphAreaX1 + $XMargin;
+        $Y = $InitialY = $this->pChartObject->GraphAreaY1 + $XMargin;
+
+        $Color = [
+            "R" => $Data["Series"][$SerieName]["Color"]["R"],
+            "G" => $Data["Series"][$SerieName]["Color"]["G"],
+            "B" => $Data["Series"][$SerieName]["Color"]["B"],
+            "Alpha" => $Data["Series"][$SerieName]["Color"]["Alpha"]
+        ];
+
+        foreach ($Points as $Key => $Point) {
+            $Value = $Data["Series"][$SerieName]["Data"][$Point];
+            $PosArray = $this->pChartObject->scaleComputeY($Value, ["AxisID" => $AxisID]);
+
+            if (isset($Data["Abscissa"]) && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Point])) {
+                $Abscissa = $Data["Series"][$Data["Abscissa"]]["Data"][$Point] . " : ";
+            } else {
+                $Abscissa = "";
+            }
+            $Value = $this->pChartObject->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
+            $Weight = $Data["Series"][$SerieWeightName]["Data"][$Point];
+            $Caption = $Abscissa . $Value . " / " . $Weight;
+
+            if (isset($Data["Series"][$SerieName]["Description"])) {
+                $Description = $Data["Series"][$SerieName]["Description"];
+            } else {
+                $Description = "No description";
+            }
+            $Series = [["Format" => $Color, "Caption" => $Caption]];
+
+            if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                if ($XDivs == 0) {
+                    $XStep = 0;
+                } else {
+                    $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2)
+                        / $XDivs
+                    ;
+                }
+
+                $X = floor($InitialX + $Point * $XStep);
+                $Y = floor($PosArray);
+            } else {
+                if ($XDivs == 0) {
+                    $YStep = 0;
+                } else {
+                    $YStep = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $XMargin * 2)
+                        / $XDivs
+                    ;
+                }
+
+                $X = floor($PosArray);
+                $Y = floor($InitialY + $Point * $YStep);
+            }
+
+            if ($DrawPoint == LABEL_POINT_CIRCLE) {
+                $this->pChartObject->drawFilledCircle(
+                    $X,
+                    $Y,
+                    3,
+                    [
+                        "R" => 255,
+                        "G" => 255,
+                        "B" => 255,
+                        "BorderR" => 0,
+                        "BorderG" => 0,
+                        "BorderB" => 0
+                    ]
+                );
+            } elseif ($DrawPoint == LABEL_POINT_BOX) {
+                $this->pChartObject->drawFilledRectangle(
+                    $X - 2,
+                    $Y - 2,
+                    $X + 2,
+                    $Y + 2,
+                    [
+                        "R" => 255,
+                        "G" => 255,
+                        "B" => 255,
+                        "BorderR" => 0,
+                        "BorderG" => 0,
+                        "BorderB" => 0
+                    ]
+                );
+            }
+
+            $this->pChartObject->drawLabelBox($X, $Y - 3, $Description, $Series, $Format);
+        }
+    }
+}

+ 377 - 0
vendor/szymach/c-pchart/src/Chart/Indicator.php

@@ -0,0 +1,377 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Image;
+
+/**
+ *  Indicator - class to draw indicators
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Indicator
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @param Image $pChartObject
+     */
+    public function __construct(Image $pChartObject)
+    {
+        $this->pChartObject = $pChartObject;
+    }
+
+    /**
+     * Draw an indicator
+     *
+     * @param int $X
+     * @param int $Y
+     * @param int $Width
+     * @param int $Height
+     * @param array $Format
+     * @return null|int
+     */
+    public function draw($X, $Y, $Width, $Height, array $Format = [])
+    {
+        $Values = isset($Format["Values"]) ? $Format["Values"] : VOID;
+        $IndicatorSections = isset($Format["IndicatorSections"]) ? $Format["IndicatorSections"] : null;
+        $ValueDisplay = isset($Format["ValueDisplay"]) ? $Format["ValueDisplay"] : INDICATOR_VALUE_BUBBLE;
+        $SectionsMargin = isset($Format["SectionsMargin"]) ? $Format["SectionsMargin"] : 4;
+        $DrawLeftHead = isset($Format["DrawLeftHead"]) ? $Format["DrawLeftHead"] : true;
+        $DrawRightHead = isset($Format["DrawRightHead"]) ? $Format["DrawRightHead"] : true;
+        $HeadSize = isset($Format["HeadSize"]) ? $Format["HeadSize"] : floor($Height / 4);
+        $TextPadding = isset($Format["TextPadding"]) ? $Format["TextPadding"] : 4;
+        $CaptionLayout = isset($Format["CaptionLayout"]) ? $Format["CaptionLayout"] : INDICATOR_CAPTION_EXTENDED;
+        $CaptionPosition = isset($Format["CaptionPosition"]) ? $Format["CaptionPosition"] : INDICATOR_CAPTION_INSIDE;
+        $CaptionColorFactor = isset($Format["CaptionColorFactor"]) ? $Format["CaptionColorFactor"] : null;
+        $CaptionR = isset($Format["CaptionR"]) ? $Format["CaptionR"] : 255;
+        $CaptionG = isset($Format["CaptionG"]) ? $Format["CaptionG"] : 255;
+        $CaptionB = isset($Format["CaptionB"]) ? $Format["CaptionB"] : 255;
+        $CaptionAlpha = isset($Format["CaptionAlpha"]) ? $Format["CaptionAlpha"] : 100;
+        $SubCaptionColorFactor = isset($Format["SubCaptionColorFactor"]) ? $Format["SubCaptionColorFactor"] : null;
+        $SubCaptionR = isset($Format["SubCaptionR"]) ? $Format["SubCaptionR"] : 50;
+        $SubCaptionG = isset($Format["SubCaptionG"]) ? $Format["SubCaptionG"] : 50;
+        $SubCaptionB = isset($Format["SubCaptionB"]) ? $Format["SubCaptionB"] : 50;
+        $SubCaptionAlpha = isset($Format["SubCaptionAlpha"]) ? $Format["SubCaptionAlpha"] : 100;
+        $ValueFontName = isset($Format["ValueFontName"]) ? $Format["ValueFontName"] : $this->pChartObject->FontName;
+        $ValueFontSize = isset($Format["ValueFontSize"]) ? $Format["ValueFontSize"] : $this->pChartObject->FontSize;
+        $CaptionFontName = isset($Format["CaptionFontName"])
+            ? $Format["CaptionFontName"] : $this->pChartObject->FontName
+        ;
+        $CaptionFontSize = isset($Format["CaptionFontSize"])
+            ? $Format["CaptionFontSize"] : $this->pChartObject->FontSize
+        ;
+        $Unit = isset($Format["Unit"]) ? $Format["Unit"] : "";
+
+        /* Convert the Values to display to an array if needed */
+        if (!is_array($Values)) {
+            $Values = [$Values];
+        }
+
+        /* No section, let's die */
+        if ($IndicatorSections == null) {
+            return 0;
+        }
+
+        /* Determine indicator visual configuration */
+        $OverallMin = $IndicatorSections[0]["End"];
+        $OverallMax = $IndicatorSections[0]["Start"];
+        foreach ($IndicatorSections as $Key => $Settings) {
+            if ($Settings["End"] > $OverallMax) {
+                $OverallMax = $Settings["End"];
+            }
+            if ($Settings["Start"] < $OverallMin) {
+                $OverallMin = $Settings["Start"];
+            }
+        }
+        $RealWidth = $Width - (count($IndicatorSections) - 1) * $SectionsMargin;
+        $XScale = $RealWidth / ($OverallMax - $OverallMin);
+
+        $X1 = $X;
+        $ValuesPos = [];
+        foreach ($IndicatorSections as $Key => $Settings) {
+            $Color = ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]];
+            $Caption = $Settings["Caption"];
+            $SubCaption = $Settings["Start"] . " - " . $Settings["End"];
+
+            $X2 = $X1 + ($Settings["End"] - $Settings["Start"]) * $XScale;
+
+            if ($Key == 0 && $DrawLeftHead) {
+                $Poly = [];
+                $Poly[] = $X1 - 1;
+                $Poly[] = $Y;
+                $Poly[] = $X1 - 1;
+                $Poly[] = $Y + $Height;
+                $Poly[] = $X1 - 1 - $HeadSize;
+                $Poly[] = $Y + ($Height / 2);
+                $this->pChartObject->drawPolygon($Poly, $Color);
+                $this->pChartObject->drawLine($X1 - 2, $Y, $X1 - 2 - $HeadSize, $Y + ($Height / 2), $Color);
+                $this->pChartObject->drawLine(
+                    $X1 - 2,
+                    $Y + $Height,
+                    $X1 - 2 - $HeadSize,
+                    $Y + ($Height / 2),
+                    $Color
+                );
+            }
+
+            /* Determine the position of the breaks */
+            $Break = [];
+            foreach ($Values as $iKey => $Value) {
+                if ($Value >= $Settings["Start"] && $Value <= $Settings["End"]) {
+                    $XBreak = $X1 + ($Value - $Settings["Start"]) * $XScale;
+                    $ValuesPos[$Value] = $XBreak;
+                    $Break[] = floor($XBreak);
+                }
+            }
+
+            if ($ValueDisplay == INDICATOR_VALUE_LABEL) {
+                if (!count($Break)) {
+                    $this->pChartObject->drawFilledRectangle($X1, $Y, $X2, $Y + $Height, $Color);
+                } else {
+                    sort($Break);
+                    $Poly = [];
+                    $Poly[] = $X1;
+                    $Poly[] = $Y;
+                    $LastPointWritten = false;
+                    foreach ($Break as $iKey => $Value) {
+                        if ($Value - 5 >= $X1) {
+                            $Poly[] = $Value - 5;
+                            $Poly[] = $Y;
+                        } elseif ($X1 - ($Value - 5) > 0) {
+                            $Offset = $X1 - ($Value - 5);
+                            $Poly = [$X1, $Y + $Offset];
+                        }
+
+                        $Poly[] = $Value;
+                        $Poly[] = $Y + 5;
+
+                        if ($Value + 5 <= $X2) {
+                            $Poly[] = $Value + 5;
+                            $Poly[] = $Y;
+                        } elseif (($Value + 5) > $X2) {
+                            $Offset = ($Value + 5) - $X2;
+                            $Poly[] = $X2;
+                            $Poly[] = $Y + $Offset;
+                            $LastPointWritten = true;
+                        }
+                    }
+                    if (!$LastPointWritten) {
+                        $Poly[] = $X2;
+                        $Poly[] = $Y;
+                    }
+                    $Poly[] = $X2;
+                    $Poly[] = $Y + $Height;
+                    $Poly[] = $X1;
+                    $Poly[] = $Y + $Height;
+
+                    $this->pChartObject->drawPolygon($Poly, $Color);
+                }
+            } else {
+                $this->pChartObject->drawFilledRectangle($X1, $Y, $X2, $Y + $Height, $Color);
+            }
+
+            if ($Key == count($IndicatorSections) - 1 && $DrawRightHead) {
+                $Poly = [];
+                $Poly[] = $X2 + 1;
+                $Poly[] = $Y;
+                $Poly[] = $X2 + 1;
+                $Poly[] = $Y + $Height;
+                $Poly[] = $X2 + 1 + $HeadSize;
+                $Poly[] = $Y + ($Height / 2);
+                $this->pChartObject->drawPolygon($Poly, $Color);
+                $this->pChartObject->drawLine($X2 + 1, $Y, $X2 + 1 + $HeadSize, $Y + ($Height / 2), $Color);
+                $this->pChartObject->drawLine(
+                    $X2 + 1,
+                    $Y + $Height,
+                    $X2 + 1 + $HeadSize,
+                    $Y + ($Height / 2),
+                    $Color
+                );
+            }
+
+            $YOffset = 0;
+            $XOffset = 0;
+            if ($CaptionPosition == INDICATOR_CAPTION_INSIDE) {
+                $TxtPos = $this->pChartObject->getTextBox(
+                    $X1,
+                    $Y + $Height + $TextPadding,
+                    $CaptionFontName,
+                    $CaptionFontSize,
+                    0,
+                    $Caption
+                );
+                $YOffset = ($TxtPos[0]["Y"] - $TxtPos[2]["Y"]) + $TextPadding;
+
+                if ($CaptionLayout == INDICATOR_CAPTION_EXTENDED) {
+                    $TxtPos = $this->pChartObject->getTextBox(
+                        $X1,
+                        $Y + $Height + $TextPadding,
+                        $CaptionFontName,
+                        $CaptionFontSize,
+                        0,
+                        $SubCaption
+                    );
+                    $YOffset = $YOffset + ($TxtPos[0]["Y"] - $TxtPos[2]["Y"]) + $TextPadding * 2;
+                }
+                $XOffset = $TextPadding;
+            }
+
+            if ($CaptionColorFactor == null) {
+                $CaptionColor = [
+                    "Align" => TEXT_ALIGN_TOPLEFT,
+                    "FontName" => $CaptionFontName,
+                    "FontSize" => $CaptionFontSize,
+                    "R" => $CaptionR,
+                    "G" => $CaptionG,
+                    "B" => $CaptionB,
+                    "Alpha" => $CaptionAlpha
+                ];
+            } else {
+                $CaptionColor = [
+                    "Align" => TEXT_ALIGN_TOPLEFT,
+                    "FontName" => $CaptionFontName,
+                    "FontSize" => $CaptionFontSize,
+                    "R" => $Settings["R"] + $CaptionColorFactor,
+                    "G" => $Settings["G"] + $CaptionColorFactor,
+                    "B" => $Settings["B"] + $CaptionColorFactor
+                ];
+            }
+
+            if ($SubCaptionColorFactor == null) {
+                $SubCaptionColor = [
+                    "Align" => TEXT_ALIGN_TOPLEFT,
+                    "FontName" => $CaptionFontName,
+                    "FontSize" => $CaptionFontSize,
+                    "R" => $SubCaptionR,
+                    "G" => $SubCaptionG,
+                    "B" => $SubCaptionB,
+                    "Alpha" => $SubCaptionAlpha
+                ];
+            } else {
+                $SubCaptionColor = [
+                    "Align" => TEXT_ALIGN_TOPLEFT,
+                    "FontName" => $CaptionFontName,
+                    "FontSize" => $CaptionFontSize,
+                    "R" => $Settings["R"] + $SubCaptionColorFactor,
+                    "G" => $Settings["G"] + $SubCaptionColorFactor,
+                    "B" => $Settings["B"] + $SubCaptionColorFactor
+                ];
+            }
+
+            $RestoreShadow = $this->pChartObject->Shadow;
+            $this->pChartObject->Shadow = false;
+
+            if ($CaptionLayout == INDICATOR_CAPTION_DEFAULT) {
+                $this->pChartObject->drawText($X1, $Y + $Height + $TextPadding, $Caption, $CaptionColor);
+            } elseif ($CaptionLayout == INDICATOR_CAPTION_EXTENDED) {
+                $TxtPos = $this->pChartObject->getTextBox(
+                    $X1,
+                    $Y + $Height + $TextPadding,
+                    $CaptionFontName,
+                    $CaptionFontSize,
+                    0,
+                    $Caption
+                );
+                $CaptionHeight = $TxtPos[0]["Y"] - $TxtPos[2]["Y"];
+
+                $this->pChartObject->drawText(
+                    $X1 + $XOffset,
+                    $Y + $Height - $YOffset + $TextPadding,
+                    $Caption,
+                    $CaptionColor
+                );
+                $this->pChartObject->drawText(
+                    $X1 + $XOffset,
+                    $Y + $Height - $YOffset + $CaptionHeight + $TextPadding * 2,
+                    $SubCaption,
+                    $SubCaptionColor
+                );
+            }
+
+            $this->pChartObject->Shadow = $RestoreShadow;
+
+            $X1 = $X2 + $SectionsMargin;
+        }
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        $this->pChartObject->Shadow = false;
+
+        foreach ($Values as $Key => $Value) {
+            if ($Value >= $OverallMin && $Value <= $OverallMax) {
+                foreach ($IndicatorSections as $Key => $Settings) {
+                    if ($Value >= $Settings["Start"] && $Value <= $Settings["End"]) {
+                        $X1 = $ValuesPos[$Value]; //$X + $Key*$SectionsMargin + ($Value - $OverallMin) * $XScale;
+
+                        if ($ValueDisplay == INDICATOR_VALUE_BUBBLE) {
+                            $TxtPos = $this->pChartObject->getTextBox(
+                                $X1,
+                                $Y,
+                                $ValueFontName,
+                                $ValueFontSize,
+                                0,
+                                $Value . $Unit
+                            );
+                            $Radius = floor(($TxtPos[1]["X"] - $TxtPos[0]["X"] + $TextPadding * 4) / 2);
+
+                            $this->pChartObject->drawFilledCircle(
+                                $X1,
+                                $Y,
+                                $Radius + 4,
+                                [
+                                    "R" => $Settings["R"] + 20,
+                                    "G" => $Settings["G"] + 20,
+                                    "B" => $Settings["B"] + 20
+                                ]
+                            );
+                            $this->pChartObject->drawFilledCircle(
+                                $X1,
+                                $Y,
+                                $Radius,
+                                ["R" => 255, "G" => 255, "B" => 255]
+                            );
+
+                            $TextSettings = [
+                                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                "FontName" => $ValueFontName,
+                                "FontSize" => $ValueFontSize
+                            ];
+                            $this->pChartObject->drawText($X1 - 1, $Y - 1, $Value . $Unit, $TextSettings);
+                        } elseif ($ValueDisplay == INDICATOR_VALUE_LABEL) {
+                            $Caption = [
+                                [
+                                    "Format" => [
+                                        "R" => $Settings["R"],
+                                        "G" => $Settings["G"],
+                                        "B" => $Settings["B"],
+                                        "Alpha" => 100
+                                    ],
+                                    "Caption" => $Value . $Unit
+                                ]
+                            ];
+                            $this->pChartObject->drawLabelBox(
+                                floor($X1),
+                                floor($Y) + 2,
+                                "Value - " . $Settings["Caption"],
+                                $Caption
+                            );
+                        }
+                    }
+                    $X1 = $X2 + $SectionsMargin;
+                }
+            }
+        }
+        $this->pChartObject->Shadow = $RestoreShadow;
+    }
+}

+ 2320 - 0
vendor/szymach/c-pchart/src/Chart/Pie.php

@@ -0,0 +1,2320 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+
+/**
+ *  Pie - class to draw pie charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Pie
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var Data
+     */
+    public $pDataObject;
+
+    /**
+     * @var array
+     */
+    public $LabelPos = [];
+
+    /**
+     * @param Image $pChartObject
+     * @param Data $pDataObject
+     */
+    public function __construct(Image $pChartObject, Data $pDataObject)
+    {
+        $this->pChartObject = $pChartObject;
+        $this->pDataObject = $pDataObject;
+    }
+
+    /**
+     * Draw a pie chart
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int
+     */
+    public function draw2DPie($X, $Y, array $Format = [])
+    {
+        $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 60;
+        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
+        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0;
+        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0;
+        $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
+        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
+        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
+        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
+        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
+        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
+        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
+        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
+        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null;
+        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15;
+        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
+        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
+        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
+        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
+        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        /* Do we have an abscissa serie defined? */
+        if ($Data["Abscissa"] == "") {
+            return PIE_NO_ABSCISSA;
+        }
+
+        /* Try to find the data serie */
+        $DataSerie = null;
+        foreach (array_keys($Data["Series"]) as $SerieName) {
+            if ($SerieName != $Data["Abscissa"]) {
+                $DataSerie = $SerieName;
+            }
+        }
+
+        /* Do we have data to compute? */
+        if (!$DataSerie) {
+            return PIE_NO_DATASERIE;
+        }
+
+        /* Remove unused data */
+        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
+
+        /* Compute the pie sum */
+        $SerieSum = $this->pDataObject->getSum($DataSerie);
+
+        /* Do we have data to draw? */
+        if ($SerieSum == 0) {
+            return PIE_SUMISNULL;
+        }
+
+        /* Dump the real number of data to draw */
+        $Values = [];
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if ($Value != 0) {
+                $Values[] = $Value;
+            }
+        }
+
+        /* Compute the wasted angular space between series */
+        if (count($Values) == 1) {
+            $WastedAngular = 0;
+        } else {
+            $WastedAngular = count($Values) * $DataGapAngle;
+        }
+
+        /* Compute the scale */
+        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        if ($this->pChartObject->Shadow) {
+            $this->pChartObject->Shadow = false;
+
+            $ShadowFormat = $Format;
+            $ShadowFormat["Shadow"] = true;
+            $this->draw2DPie(
+                $X + $this->pChartObject->ShadowX,
+                $Y + $this->pChartObject->ShadowY,
+                $ShadowFormat
+            );
+        }
+
+        /* Draw the polygon pie elements */
+        $Step = 360 / (2 * PI * $Radius);
+        $Offset = 0;
+        $ID = 0;
+        foreach ($Values as $Key => $Value) {
+            if ($Shadow) {
+                $Settings = [
+                    "R" => $this->pChartObject->ShadowR,
+                    "G" => $this->pChartObject->ShadowG,
+                    "B" => $this->pChartObject->ShadowB,
+                    "Alpha" => $this->pChartObject->Shadowa
+                ];
+            } else {
+                if (!isset($Palette[$ID]["R"])) {
+                    $Color = $this->pChartObject->getRandomColor();
+                    $Palette[$ID] = $Color;
+                    $this->pDataObject->savePalette($ID, $Color);
+                }
+                $Settings = [
+                    "R" => $Palette[$ID]["R"],
+                    "G" => $Palette[$ID]["G"],
+                    "B" => $Palette[$ID]["B"],
+                    "Alpha" => $Palette[$ID]["Alpha"]
+                ];
+            }
+
+            if (!$SecondPass && !$Shadow) {
+                if (!$Border) {
+                    $Settings["Surrounding"] = 10;
+                } else {
+                    $Settings["BorderR"] = $BorderR;
+                    $Settings["BorderG"] = $BorderG;
+                    $Settings["BorderB"] = $BorderB;
+                }
+            }
+
+            $EndAngle = $Offset + ($Value * $ScaleFactor);
+            if ($EndAngle > 360) {
+                $EndAngle = 360;
+            }
+
+            $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+            if ($DataGapAngle == 0) {
+                $X0 = $X;
+                $Y0 = $Y;
+            } else {
+                $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
+                $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y;
+            }
+
+            $Plots = [$X0, $Y0];
+
+            for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
+                $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y;
+
+                if ($SecondPass && ($i < 90)) {
+                    $Yc++;
+                }
+                if ($SecondPass && ($i > 180 && $i < 270)) {
+                    $Xc++;
+                }
+                if ($SecondPass && ($i >= 270)) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Plots[] = $Xc;
+                $Plots[] = $Yc;
+            }
+
+            $this->pChartObject->drawPolygon($Plots, $Settings);
+            if ($RecordImageMap && !$Shadow) {
+                $this->pChartObject->addToImageMap(
+                    "POLY",
+                    $this->arraySerialize($Plots),
+                    $this->pChartObject->toHTMLColor(
+                        $Palette[$ID]["R"],
+                        $Palette[$ID]["G"],
+                        $Palette[$ID]["B"]
+                    ),
+                    $Data["Series"][$Data["Abscissa"]]["Data"][$Key],
+                    $Value
+                );
+            }
+
+            if ($DrawLabels && !$Shadow && !$SecondPass) {
+                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
+                    $Settings = [
+                        "FillR" => $Palette[$ID]["R"],
+                        "FillG" => $Palette[$ID]["G"],
+                        "FillB" => $Palette[$ID]["B"],
+                        "Alpha" => $Palette[$ID]["Alpha"]
+                    ];
+                } else {
+                    $Settings = [
+                        "FillR" => $LabelR,
+                        "FillG" => $LabelG,
+                        "FillB" => $LabelB,
+                        "Alpha" => $LabelAlpha
+                    ];
+                }
+
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y;
+
+                $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
+
+                if ($LabelStacked) {
+                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius);
+                } else {
+                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
+                }
+            }
+
+            $Offset = $i + $DataGapAngle;
+            $ID++;
+        }
+
+        /* Second pass to smooth the angles */
+        if ($SecondPass) {
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 0;
+            $ID = 0;
+            foreach ($Values as $Key => $Value) {
+                $FirstPoint = true;
+                if ($Shadow) {
+                    $Settings = [
+                        "R" => $this->pChartObject->ShadowR,
+                        "G" => $this->pChartObject->ShadowG,
+                        "B" => $this->pChartObject->ShadowB,
+                        "Alpha" => $this->pChartObject->Shadowa
+                    ];
+                } else {
+                    if ($Border) {
+                        $Settings = ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB];
+                    } else {
+                        $Settings = [
+                            "R" => $Palette[$ID]["R"],
+                            "G" => $Palette[$ID]["G"],
+                            "B" => $Palette[$ID]["B"],
+                            "Alpha" => $Palette[$ID]["Alpha"]
+                        ];
+                    }
+                }
+
+                $EndAngle = $Offset + ($Value * $ScaleFactor);
+                if ($EndAngle > 360) {
+                    $EndAngle = 360;
+                }
+
+                if ($DataGapAngle == 0) {
+                    $X0 = $X;
+                    $Y0 = $Y;
+                } else {
+                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                    $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
+                    $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y;
+                }
+                $Plots[] = $X0;
+                $Plots[] = $Y0;
+
+                for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
+                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y;
+
+                    if ($FirstPoint) {
+                        $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
+                        $FirstPoint = false;
+                    }
+
+                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
+                }
+                $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
+
+                if ($DrawLabels && !$Shadow) {
+                    if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
+                        $Settings = [
+                            "FillR" => $Palette[$ID]["R"],
+                            "FillG" => $Palette[$ID]["G"],
+                            "FillB" => $Palette[$ID]["B"],
+                            "Alpha" => $Palette[$ID]["Alpha"]
+                        ];
+                    } else {
+                        $Settings = [
+                            "FillR" => $LabelR,
+                            "FillG" => $LabelG,
+                            "FillB" => $LabelB,
+                            "Alpha" => $LabelAlpha
+                        ];
+                    }
+
+                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y;
+
+                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
+
+                    if ($LabelStacked) {
+                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius);
+                    } else {
+                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
+                    }
+                }
+
+                $Offset = $i + $DataGapAngle;
+                $ID++;
+            }
+        }
+
+        if ($WriteValues != null && !$Shadow) {
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 0;
+            $ID = count($Values) - 1;
+            $Settings = [
+                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                "R" => $ValueR,
+                "G" => $ValueG,
+                "B" => $ValueB,
+                "Alpha" => $ValueAlpha
+            ];
+            foreach ($Values as $Key => $Value) {
+                $EndAngle = ($Value * $ScaleFactor) + $Offset;
+                if ((int) $EndAngle > 360) {
+                    $EndAngle = 0;
+                }
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+
+                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
+                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $Y;
+                } else {
+                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius) / 2 + $Y;
+                }
+
+                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
+                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
+                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
+                    $Display = $Value . $ValueSuffix;
+                }
+                $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings);
+
+                $Offset = $EndAngle + $DataGapAngle;
+                $ID--;
+            }
+        }
+
+        if ($DrawLabels && $LabelStacked) {
+            $this->writeShiftedLabels();
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+
+        return PIE_RENDERED;
+    }
+
+    /**
+     * Draw a 3D pie chart
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int
+     */
+    public function draw3DPie($X, $Y, array $Format = [])
+    {
+        /* Rendering layout */
+        $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 80;
+        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
+        $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .5;
+        $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 20;
+        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0;
+        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0;
+        $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
+        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
+        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
+        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
+        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
+        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
+        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
+        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
+        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE
+        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_INSIDE;
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15;
+        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
+        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
+        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
+        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
+        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        /* Error correction for overlaying rounded corners */
+        if ($SkewFactor < .5) {
+            $SkewFactor = .5;
+        }
+
+        /* Data Processing */
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        /* Do we have an abscissa serie defined? */
+        if ($Data["Abscissa"] == "") {
+            return PIE_NO_ABSCISSA;
+        }
+
+        /* Try to find the data serie */
+        $DataSerie = null;
+        foreach ($Data["Series"] as $SerieName => $SerieData) {
+            if ($SerieName != $Data["Abscissa"]) {
+                $DataSerie = $SerieName;
+            }
+        }
+
+        /* Do we have data to compute? */
+        if (!$DataSerie) {
+            return PIE_NO_DATASERIE;
+        }
+
+        /* Remove unused data */
+        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
+
+        /* Compute the pie sum */
+        $SerieSum = $this->pDataObject->getSum($DataSerie);
+
+        /* Do we have data to draw? */
+        if ($SerieSum == 0) {
+            return PIE_SUMISNULL;
+        }
+
+        /* Dump the real number of data to draw */
+        $Values = [];
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if ($Value != 0) {
+                $Values[] = $Value;
+            }
+        }
+
+        /* Compute the wasted angular space between series */
+        if (count($Values) == 1) {
+            $WastedAngular = 0;
+        } else {
+            $WastedAngular = count($Values) * $DataGapAngle;
+        }
+
+        /* Compute the scale */
+        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        if ($this->pChartObject->Shadow) {
+            $this->pChartObject->Shadow = false;
+        }
+
+        /* Draw the polygon pie elements */
+        $Step = 360 / (2 * PI * $Radius);
+        $Offset = 360;
+        $ID = count($Values) - 1;
+        $Values = array_reverse($Values);
+        $Slice = 0;
+        $Slices = [];
+        $SliceColors = [];
+        $Visible = [];
+        $SliceAngle = [];
+        foreach ($Values as $Key => $Value) {
+            if (!isset($Palette[$ID]["R"])) {
+                $Color = $this->pChartObject->getRandomColor();
+                $Palette[$ID] = $Color;
+                $this->pDataObject->savePalette($ID, $Color);
+            }
+            $Settings = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"]
+            ];
+
+            $SliceColors[$Slice] = $Settings;
+
+            $StartAngle = $Offset;
+            $EndAngle = $Offset - ($Value * $ScaleFactor);
+            if ($EndAngle < 0) {
+                $EndAngle = 0;
+            }
+
+            if ($StartAngle > 180) {
+                $Visible[$Slice]["Start"] = true;
+            } else {
+                $Visible[$Slice]["Start"] = true;
+            }
+            if ($EndAngle < 180) {
+                $Visible[$Slice]["End"] = false;
+            } else {
+                $Visible[$Slice]["End"] = true;
+            }
+
+            if ($DataGapAngle == 0) {
+                $X0 = $X;
+                $Y0 = $Y;
+            } else {
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
+                $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y;
+            }
+            $Slices[$Slice][] = $X0;
+            $Slices[$Slice][] = $Y0;
+            $SliceAngle[$Slice][] = 0;
+
+            for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
+                $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+
+                if (($SecondPass || $RestoreShadow) && ($i < 90)) {
+                    $Yc++;
+                }
+                if (($SecondPass || $RestoreShadow) && ($i > 90 && $i < 180)) {
+                    $Xc++;
+                }
+                if (($SecondPass || $RestoreShadow) && ($i > 180 && $i < 270)) {
+                    $Xc++;
+                }
+                if (($SecondPass || $RestoreShadow) && ($i >= 270)) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Slices[$Slice][] = $Xc;
+                $Slices[$Slice][] = $Yc;
+                $SliceAngle[$Slice][] = $i;
+            }
+
+            $Offset = $i - $DataGapAngle;
+            $ID--;
+            $Slice++;
+        }
+
+        /* Draw the bottom shadow if needed */
+        if ($RestoreShadow && ($this->pChartObject->ShadowX != 0 || $this->pChartObject->ShadowY != 0)) {
+            foreach ($Slices as $SliceID => $Plots) {
+                $ShadowPie = [];
+                for ($i = 0; $i < count($Plots); $i = $i + 2) {
+                    $ShadowPie[] = $Plots[$i] + $this->pChartObject->ShadowX;
+                    $ShadowPie[] = $Plots[$i + 1] + $this->pChartObject->ShadowY;
+                }
+
+                $Settings = [
+                    "R" => $this->pChartObject->ShadowR,
+                    "G" => $this->pChartObject->ShadowG,
+                    "B" => $this->pChartObject->ShadowB,
+                    "Alpha" => $this->pChartObject->Shadowa,
+                    "NoBorder" => true
+                ];
+                $this->pChartObject->drawPolygon($ShadowPie, $Settings);
+            }
+
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 360;
+            foreach ($Values as $Key => $Value) {
+                $EndAngle = $Offset - ($Value * $ScaleFactor);
+                if ($EndAngle < 0) {
+                    $EndAngle = 0;
+                }
+
+                for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
+                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X + $this->pChartObject->ShadowX;
+                    $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y + $this->pChartObject->ShadowY;
+
+                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
+                }
+
+                $Offset = $i - $DataGapAngle;
+                $ID--;
+            }
+        }
+
+        /* Draw the bottom pie splice */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $this->pChartObject->drawPolygon($Plots, $Settings);
+
+            if ($SecondPass) {
+                $Settings = $SliceColors[$SliceID];
+                if ($Border) {
+                    $Settings["R"] += 30;
+                    $Settings["G"] += 30;
+                    $Settings["B"] += 30;
+                }
+                /* Empty error handling */
+                if (isset($SliceAngle[$SliceID][1])) {
+                    $Angle = $SliceAngle[$SliceID][1];
+                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+                    $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings);
+
+                    $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1];
+                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+                    $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings);
+                }
+            }
+        }
+
+        /* Draw the two vertical edges */
+        $Slices = array_reverse($Slices);
+        $SliceColors = array_reverse($SliceColors);
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["R"] += 10;
+            $Settings["G"] += 10;
+            $Settings["B"] += 10;
+            $Settings["NoBorder"] = true;
+            /* Empty error handling */
+            if ($Visible[$SliceID]["Start"] && isset($Plots[2])) {
+                $this->pChartObject->drawLine(
+                    $Plots[2],
+                    $Plots[3],
+                    $Plots[2],
+                    $Plots[3] - $SliceHeight,
+                    ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]]
+                );
+                $Border = [];
+                $Border[] = $Plots[0];
+                $Border[] = $Plots[1];
+                $Border[] = $Plots[0];
+                $Border[] = $Plots[1] - $SliceHeight;
+                $Border[] = $Plots[2];
+                $Border[] = $Plots[3] - $SliceHeight;
+                $Border[] = $Plots[2];
+                $Border[] = $Plots[3];
+                $this->pChartObject->drawPolygon($Border, $Settings);
+            }
+        }
+
+        $Slices = array_reverse($Slices);
+        $SliceColors = array_reverse($SliceColors);
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["R"] += 10;
+            $Settings["G"] += 10;
+            $Settings["B"] += 10;
+            $Settings["NoBorder"] = true;
+            if ($Visible[$SliceID]["End"]) {
+                $this->pChartObject->drawLine(
+                    $Plots[count($Plots) - 2],
+                    $Plots[count($Plots) - 1],
+                    $Plots[count($Plots) - 2],
+                    $Plots[count($Plots) - 1] - $SliceHeight,
+                    ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]]
+                );
+
+                $Border = [];
+                $Border[] = $Plots[0];
+                $Border[] = $Plots[1];
+                $Border[] = $Plots[0];
+                $Border[] = $Plots[1] - $SliceHeight;
+                $Border[] = $Plots[count($Plots) - 2];
+                $Border[] = $Plots[count($Plots) - 1] - $SliceHeight;
+                $Border[] = $Plots[count($Plots) - 2];
+                $Border[] = $Plots[count($Plots) - 1];
+                $this->pChartObject->drawPolygon($Border, $Settings);
+            }
+        }
+
+        /* Draw the rounded edges */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["R"] += 10;
+            $Settings["G"] += 10;
+            $Settings["B"] += 10;
+            $Settings["NoBorder"] = true;
+
+            for ($j = 2; $j < count($Plots) - 2; $j = $j + 2) {
+                $Angle = $SliceAngle[$SliceID][$j / 2];
+                if ($Angle < 270 && $Angle > 90) {
+                    $Border = [];
+                    $Border[] = $Plots[$j];
+                    $Border[] = $Plots[$j + 1];
+                    $Border[] = $Plots[$j + 2];
+                    $Border[] = $Plots[$j + 3];
+                    $Border[] = $Plots[$j + 2];
+                    $Border[] = $Plots[$j + 3] - $SliceHeight;
+                    $Border[] = $Plots[$j];
+                    $Border[] = $Plots[$j + 1] - $SliceHeight;
+                    $this->pChartObject->drawPolygon($Border, $Settings);
+                }
+            }
+
+            if ($SecondPass) {
+                $Settings = $SliceColors[$SliceID];
+                if (count($Border)) {
+                    $Settings["R"] += 30;
+                    $Settings["G"] += 30;
+                    $Settings["B"] += 30;
+                }
+
+                /* Empty error handling */
+                if (isset($SliceAngle[$SliceID][1])) {
+                    $Angle = $SliceAngle[$SliceID][1];
+                    if ($Angle < 270 && $Angle > 90) {
+                        $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                        $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+                        $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
+                    }
+                }
+
+                $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1];
+                if ($Angle < 270 && $Angle > 90) {
+                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
+                }
+
+                if (isset($SliceAngle[$SliceID][1])
+                    && $SliceAngle[$SliceID][1] > 270
+                    && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 270
+                ) {
+                    $Xc = cos((270 - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin((270 - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
+                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
+                }
+
+                if (isset($SliceAngle[$SliceID][1])
+                    && $SliceAngle[$SliceID][1] > 90
+                    && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 90
+                ) {
+                    $Xc = cos((0) * PI / 180) * $Radius + $X;
+                    $Yc = sin((0) * PI / 180) * $Radius * $SkewFactor + $Y;
+                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
+                }
+            }
+        }
+
+        /* Draw the top splice */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["R"] += 20;
+            $Settings["G"] += 20;
+            $Settings["B"] += 20;
+
+            $Top = [];
+            for ($j = 0; $j < count($Plots); $j = $j + 2) {
+                $Top[] = $Plots[$j];
+                $Top[] = $Plots[$j + 1] - $SliceHeight;
+            }
+            $this->pChartObject->drawPolygon($Top, $Settings);
+
+            if ($RecordImageMap && !$Shadow) {
+                $this->pChartObject->addToImageMap(
+                    "POLY",
+                    $this->arraySerialize($Top),
+                    $this->pChartObject->toHTMLColor(
+                        $Settings["R"],
+                        $Settings["G"],
+                        $Settings["B"]
+                    ),
+                    $Data["Series"][$Data["Abscissa"]]["Data"][count($Slices) - $SliceID - 1],
+                    $Values[$SliceID]
+                );
+            }
+        }
+
+
+        /* Second pass to smooth the angles */
+        if ($SecondPass) {
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 360;
+            $ID = count($Values) - 1;
+            foreach ($Values as $Key => $Value) {
+                $FirstPoint = true;
+                if ($Shadow) {
+                    $Settings = [
+                        "R" => $this->pChartObject->ShadowR,
+                        "G" => $this->pChartObject->ShadowG,
+                        "B" => $this->pChartObject->ShadowB,
+                        "Alpha" => $this->pChartObject->Shadowa
+                    ];
+                } else {
+                    if ($Border) {
+                        $Settings = [
+                            "R" => $Palette[$ID]["R"] + 30,
+                            "G" => $Palette[$ID]["G"] + 30,
+                            "B" => $Palette[$ID]["B"] + 30,
+                            "Alpha" => $Palette[$ID]["Alpha"]
+                        ];
+                    } else {
+                        $Settings = [
+                            "R" => $Palette[$ID]["R"],
+                            "G" => $Palette[$ID]["G"],
+                            "B" => $Palette[$ID]["B"],
+                            "Alpha" => $Palette[$ID]["Alpha"]
+                        ];
+                    }
+                }
+
+                $EndAngle = $Offset - ($Value * $ScaleFactor);
+                if ($EndAngle < 0) {
+                    $EndAngle = 0;
+                }
+
+                if ($DataGapAngle == 0) {
+                    $X0 = $X;
+                    $Y0 = $Y - $SliceHeight;
+                } else {
+                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                    $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
+                    $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y - $SliceHeight;
+                }
+                $Plots[] = $X0;
+                $Plots[] = $Y0;
+
+                for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
+                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
+                    $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight;
+
+                    if ($FirstPoint) {
+                        $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
+                        $FirstPoint = false;
+                    }
+
+                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
+                    if ($i < 270 && $i > 90) {
+                        $this->pChartObject->drawAntialiasPixel($Xc, $Yc + $SliceHeight, $Settings);
+                    }
+                }
+                $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
+
+                $Offset = $i - $DataGapAngle;
+                $ID--;
+            }
+        }
+
+        if ($WriteValues != null) {
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 360;
+            $ID = count($Values) - 1;
+            $Settings = [
+                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                "R" => $ValueR,
+                "G" => $ValueG,
+                "B" => $ValueB,
+                "Alpha" => $ValueAlpha
+            ];
+            foreach ($Values as $Key => $Value) {
+                $EndAngle = $Offset - ($Value * $ScaleFactor);
+                if ($EndAngle < 0) {
+                    $EndAngle = 0;
+                }
+
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+
+                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
+                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180)
+                        * (($Radius * $SkewFactor) + $ValuePadding)
+                        + $Y - $SliceHeight
+                    ;
+                } else {
+                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius * $SkewFactor) / 2 + $Y - $SliceHeight;
+                }
+
+                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
+                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
+                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
+                    $Display = $Value . $ValueSuffix;
+                }
+
+                $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings);
+
+                $Offset = $EndAngle - $DataGapAngle;
+                $ID--;
+            }
+        }
+
+        if ($DrawLabels) {
+            $Step = 360 / (2 * PI * $Radius);
+            $Offset = 360;
+            $ID = count($Values) - 1;
+            foreach ($Values as $Key => $Value) {
+                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
+                    $Settings = [
+                        "FillR" => $Palette[$ID]["R"],
+                        "FillG" => $Palette[$ID]["G"],
+                        "FillB" => $Palette[$ID]["B"],
+                        "Alpha" => $Palette[$ID]["Alpha"]
+                    ];
+                } else {
+                    $Settings = [
+                        "FillR" => $LabelR,
+                        "FillG" => $LabelG,
+                        "FillB" => $LabelB,
+                        "Alpha" => $LabelAlpha
+                    ];
+                }
+
+                $EndAngle = $Offset - ($Value * $ScaleFactor);
+                if ($EndAngle < 0) {
+                    $EndAngle = 0;
+                }
+
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
+                $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight;
+
+                if (isset($Data["Series"][$Data["Abscissa"]]["Data"][$ID])) {
+                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$ID];
+
+                    if ($LabelStacked) {
+                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius, true);
+                    } else {
+                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
+                    }
+                }
+
+                $Offset = $EndAngle - $DataGapAngle;
+                $ID--;
+            }
+        }
+
+        if ($DrawLabels && $LabelStacked) {
+            $this->writeShiftedLabels();
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+
+        return PIE_RENDERED;
+    }
+
+    /**
+     * Draw the legend of pie chart
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int
+     */
+    public function drawPieLegend($X, $Y, array $Format = [])
+    {
+        $FontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize;
+        $FontR = isset($Format["FontR"]) ? $Format["FontR"] : $this->pChartObject->FontColorR;
+        $FontG = isset($Format["FontG"]) ? $Format["FontG"] : $this->pChartObject->FontColorG;
+        $FontB = isset($Format["FontB"]) ? $Format["FontB"] : $this->pChartObject->FontColorB;
+        $BoxSize = isset($Format["BoxSize"]) ? $Format["BoxSize"] : 5;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
+        $R = isset($Format["R"]) ? $Format["R"] : 200;
+        $G = isset($Format["G"]) ? $Format["G"] : 200;
+        $B = isset($Format["B"]) ? $Format["B"] : 200;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Style = isset($Format["Style"]) ? $Format["Style"] : LEGEND_ROUND;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        $YStep = max($this->pChartObject->FontSize, $BoxSize) + 5;
+        $XStep = $BoxSize + 5;
+
+        /* Data Processing */
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        /* Do we have an abscissa serie defined? */
+        if ($Data["Abscissa"] == "") {
+            return PIE_NO_ABSCISSA;
+        }
+
+        $Boundaries = [];
+        $Boundaries["L"] = $X;
+        $Boundaries["T"] = $Y;
+        $Boundaries["R"] = 0;
+        $Boundaries["B"] = 0;
+        $vY = $Y;
+        $vX = $X;
+        foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) {
+            $BoxArray = $this->pChartObject->getTextBox(
+                $vX + $BoxSize + 4,
+                $vY + $BoxSize / 2,
+                $FontName,
+                $FontSize,
+                0,
+                $Value
+            );
+
+            if ($Mode == LEGEND_VERTICAL) {
+                if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) {
+                    $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2;
+                }
+                if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                    $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                }
+                if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) {
+                    $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2;
+                }
+                $vY = $vY + $YStep;
+            } elseif ($Mode == LEGEND_HORIZONTAL) {
+                if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) {
+                    $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2;
+                }
+                if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                    $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                }
+                if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) {
+                    $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2;
+                }
+                $vX = $Boundaries["R"] + $XStep;
+            }
+        }
+        $vY = $vY - $YStep;
+        $vX = $vX - $XStep;
+
+        $TopOffset = $Y - $Boundaries["T"];
+        if ($Boundaries["B"] - ($vY + $BoxSize) < $TopOffset) {
+            $Boundaries["B"] = $vY + $BoxSize + $TopOffset;
+        }
+
+        if ($Style == LEGEND_ROUND) {
+            $this->pChartObject->drawRoundedFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        } elseif ($Style == LEGEND_BOX) {
+            $this->pChartObject->drawFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        }
+        $RestoreShadow = $this->pChartObject->Shadow;
+        $this->pChartObject->Shadow = false;
+        foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) {
+            $R = $Palette[$Key]["R"];
+            $G = $Palette[$Key]["G"];
+            $B = $Palette[$Key]["B"];
+
+            $this->pChartObject->drawFilledRectangle(
+                $X + 1,
+                $Y + 1,
+                $X + $BoxSize + 1,
+                $Y + $BoxSize + 1,
+                ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
+            );
+            $this->pChartObject->drawFilledRectangle(
+                $X,
+                $Y,
+                $X + $BoxSize,
+                $Y + $BoxSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
+            );
+            if ($Mode == LEGEND_VERTICAL) {
+                $this->pChartObject->drawText(
+                    $X + $BoxSize + 4,
+                    $Y + $BoxSize / 2,
+                    $Value,
+                    [
+                        "R" => $FontR,
+                        "G" => $FontG,
+                        "B" => $FontB,
+                        "Align" => TEXT_ALIGN_MIDDLELEFT,
+                        "FontName" => $FontName,
+                        "FontSize" => $FontSize
+                    ]
+                );
+                $Y = $Y + $YStep;
+            } elseif ($Mode == LEGEND_HORIZONTAL) {
+                $BoxArray = $this->pChartObject->drawText(
+                    $X + $BoxSize + 4,
+                    $Y + $BoxSize / 2,
+                    $Value,
+                    [
+                        "R" => $FontR,
+                        "G" => $FontG,
+                        "B" => $FontB,
+                        "Align" => TEXT_ALIGN_MIDDLELEFT,
+                        "FontName" => $FontName,
+                        "FontSize" => $FontSize
+                    ]
+                );
+                $X = $BoxArray[1]["X"] + 2 + $XStep;
+            }
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Set the color of the specified slice
+     * @param mixed $SliceID
+     * @param array $Format
+     */
+    public function setSliceColor($SliceID, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+        $this->pDataObject->Palette[$SliceID]["R"] = $R;
+        $this->pDataObject->Palette[$SliceID]["G"] = $G;
+        $this->pDataObject->Palette[$SliceID]["B"] = $B;
+        $this->pDataObject->Palette[$SliceID]["Alpha"] = $Alpha;
+    }
+
+    /**
+     * Internally used compute the label positions
+     * @param int $X
+     * @param int $Y
+     * @param string $Label
+     * @param int|float $Angle
+     * @param array $Settings
+     * @param boolean $Stacked
+     * @param int $Xc
+     * @param int $Yc
+     * @param int $Radius
+     * @param boolean $Reversed
+     */
+    public function writePieLabel(
+        $X,
+        $Y,
+        $Label,
+        $Angle,
+        $Settings,
+        $Stacked,
+        $Xc = 0,
+        $Yc = 0,
+        $Radius = 0,
+        $Reversed = false
+    ) {
+        $LabelOffset = 30;
+        $FontName = $this->pChartObject->FontName;
+        $FontSize = $this->pChartObject->FontSize;
+
+        if (!$Stacked) {
+            $Settings["Angle"] = 360 - $Angle;
+            $Settings["Length"] = 25;
+            $Settings["Size"] = 8;
+
+            $this->pChartObject->drawArrowLabel($X, $Y, " " . $Label . " ", $Settings);
+        } else {
+            $X2 = cos(deg2rad($Angle - 90)) * 20 + $X;
+            $Y2 = sin(deg2rad($Angle - 90)) * 20 + $Y;
+
+            $TxtPos = $this->pChartObject->getTextBox($X, $Y, $FontName, $FontSize, 0, $Label);
+            $Height = $TxtPos[0]["Y"] - $TxtPos[2]["Y"];
+            $YTop = $Y2 - $Height / 2 - 2;
+            $YBottom = $Y2 + $Height / 2 + 2;
+
+            if (count($this->LabelPos)) {
+                $Done = false;
+                foreach ($this->LabelPos as $Key => $Settings) {
+                    if (!$Done) {
+                        $yTopAboveTopBelowBottom = ($YTop >= $Settings["YTop"] && $YTop <= $Settings["YBottom"]);
+                        $yBottomAboveTopBelowBottom = ($YBottom >= $Settings["YTop"]
+                            && $YBottom <= $Settings["YBottom"]
+                        );
+
+                        if ($Angle <= 90
+                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
+                        ) {
+                            $this->shift(0, 180, -($Height + 2), $Reversed);
+                            $Done = true;
+                        }
+                        if ($Angle > 90
+                            && $Angle <= 180
+                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
+                        ) {
+                            $this->shift(0, 180, -($Height + 2), $Reversed);
+                            $Done = true;
+                        }
+                        if ($Angle > 180
+                            && $Angle <= 270
+                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
+                        ) {
+                            $this->shift(180, 360, ($Height + 2), $Reversed);
+                            $Done = true;
+                        }
+                        if ($Angle > 270
+                            && $Angle <= 360
+                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
+                        ) {
+                            $this->shift(180, 360, ($Height + 2), $Reversed);
+                            $Done = true;
+                        }
+                    }
+                }
+            }
+
+            $LabelSettings = [
+                "YTop" => $YTop,
+                "YBottom" => $YBottom,
+                "Label" => $Label,
+                "Angle" => $Angle,
+                "X1" => $X,
+                "Y1" => $Y,
+                "X2" => $X2,
+                "Y2" => $Y2
+            ];
+            if ($Angle <= 180) {
+                $LabelSettings["X3"] = $Xc + $Radius + $LabelOffset;
+            }
+            if ($Angle > 180) {
+                $LabelSettings["X3"] = $Xc - $Radius - $LabelOffset;
+            }
+            $this->LabelPos[] = $LabelSettings;
+        }
+    }
+
+    /**
+     * Internally used to shift label positions
+     * @param int $StartAngle
+     * @param int $EndAngle
+     * @param int $Offset
+     * @param boolean $Reversed
+     */
+    public function shift($StartAngle, $EndAngle, $Offset, $Reversed)
+    {
+        if ($Reversed) {
+            $Offset = -$Offset;
+        }
+        foreach ($this->LabelPos as $Key => $Settings) {
+            if ($Settings["Angle"] > $StartAngle && $Settings["Angle"] <= $EndAngle) {
+                $this->LabelPos[$Key]["YTop"] = $Settings["YTop"] + $Offset;
+                $this->LabelPos[$Key]["YBottom"] = $Settings["YBottom"] + $Offset;
+                $this->LabelPos[$Key]["Y2"] = $Settings["Y2"] + $Offset;
+            }
+        }
+    }
+
+    /**
+     * Internally used to write the re-computed labels
+     * @return null|int
+     */
+    public function writeShiftedLabels()
+    {
+        if (!count($this->LabelPos)) {
+            return 0;
+        }
+        foreach ($this->LabelPos as $Settings) {
+            $X1 = $Settings["X1"];
+            $Y1 = $Settings["Y1"];
+            $X2 = $Settings["X2"];
+            $Y2 = $Settings["Y2"];
+            $X3 = $Settings["X3"];
+            $Angle = $Settings["Angle"];
+            $Label = $Settings["Label"];
+
+            $this->pChartObject->drawArrow($X2, $Y2, $X1, $Y1, ["Size" => 8]);
+            if ($Angle <= 180) {
+                $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2);
+                $this->pChartObject->drawText($X3 + 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLELEFT]);
+            } else {
+                $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2);
+                $this->pChartObject->drawText($X3 - 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLERIGHT]);
+            }
+        }
+    }
+
+    /**
+     * Draw a ring chart
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int
+     */
+    public function draw2DRing($X, $Y, array $Format = [])
+    {
+        $OuterRadius = isset($Format["Radius"]) ? $Format["Radius"] : 60; // For compatibility
+        $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : $OuterRadius;
+        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
+        $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 100;
+        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
+        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
+        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
+        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
+        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
+        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
+        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
+        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 5;
+        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
+        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
+        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
+        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
+        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
+        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        /* Data Processing */
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        /* Do we have an abscissa serie defined? */
+        if ($Data["Abscissa"] == "") {
+            return PIE_NO_ABSCISSA;
+        }
+
+        /* Try to find the data serie */
+        $DataSerie = null;
+        foreach ($Data["Series"] as $SerieName => $SerieData) {
+            if ($SerieName != $Data["Abscissa"]) {
+                $DataSerie = $SerieName;
+            }
+        }
+
+        /* Do we have data to compute? */
+        if (!$DataSerie) {
+            return PIE_NO_DATASERIE;
+        }
+
+        /* Remove unused data */
+        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
+
+        /* Compute the pie sum */
+        $SerieSum = $this->pDataObject->getSum($DataSerie);
+
+        /* Do we have data to draw? */
+        if ($SerieSum == 0) {
+            return PIE_SUMISNULL;
+        }
+
+        /* Dump the real number of data to draw */
+        $Values = [];
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if ($Value != 0) {
+                $Values[] = $Value;
+            }
+        }
+
+        /* Compute the wasted angular space between series */
+        if (count($Values) == 1) {
+            $WastedAngular = 0;
+        } else {
+            $WastedAngular = 0;
+        } // count($Values)
+
+        /* Compute the scale */
+        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        if ($this->pChartObject->Shadow) {
+            $this->pChartObject->Shadow = false;
+
+            $ShadowFormat = $Format;
+            $ShadowFormat["Shadow"] = true;
+            $this->draw2DRing(
+                $X + $this->pChartObject->ShadowX,
+                $Y + $this->pChartObject->ShadowY,
+                $ShadowFormat
+            );
+        }
+
+        /* Draw the polygon pie elements */
+        $Step = 360 / (2 * PI * $OuterRadius);
+        $Offset = 0;
+        $ID = 0;
+        foreach ($Values as $Key => $Value) {
+            if ($Shadow) {
+                $Settings = [
+                    "R" => $this->pChartObject->ShadowR,
+                    "G" => $this->pChartObject->ShadowG,
+                    "B" => $this->pChartObject->ShadowB,
+                    "Alpha" => $this->pChartObject->Shadowa
+                ];
+                $BorderColor = $Settings;
+            } else {
+                if (!isset($Palette[$ID]["R"])) {
+                    $Color = $this->pChartObject->getRandomColor();
+                    $Palette[$ID] = $Color;
+                    $this->pDataObject->savePalette($ID, $Color);
+                }
+                $Settings = [
+                    "R" => $Palette[$ID]["R"],
+                    "G" => $Palette[$ID]["G"],
+                    "B" => $Palette[$ID]["B"],
+                    "Alpha" => $Palette[$ID]["Alpha"]
+                ];
+
+                if ($Border) {
+                    $BorderColor = [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha
+                    ];
+                } else {
+                    $BorderColor = $Settings;
+                }
+            }
+
+            $Plots = [];
+            $Boundaries = [];
+            $AAPixels = [];
+            $EndAngle = $Offset + ($Value * $ScaleFactor);
+            if ($EndAngle > 360) {
+                $EndAngle = 360;
+            }
+            for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * $OuterRadius + $X;
+                $Yc = sin(($i - 90) * PI / 180) * $OuterRadius + $Y;
+
+                if (!isset($Boundaries[0]["X1"])) {
+                    $Boundaries[0]["X1"] = $Xc;
+                    $Boundaries[0]["Y1"] = $Yc;
+                }
+                $AAPixels[] = [$Xc, $Yc];
+
+                if ($i < 90) {
+                    $Yc++;
+                }
+                if ($i > 180 && $i < 270) {
+                    $Xc++;
+                }
+                if ($i >= 270) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Plots[] = $Xc;
+                $Plots[] = $Yc;
+            }
+            $Boundaries[1]["X1"] = $Xc;
+            $Boundaries[1]["Y1"] = $Yc;
+            $Lasti = $EndAngle;
+
+            for ($i = $EndAngle; $i >= $Offset; $i = $i - $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius - 1) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius - 1) + $Y;
+
+                if (!isset($Boundaries[1]["X2"])) {
+                    $Boundaries[1]["X2"] = $Xc;
+                    $Boundaries[1]["Y2"] = $Yc;
+                }
+                $AAPixels[] = [$Xc, $Yc];
+
+                $Xc = cos(($i - 90) * PI / 180) * $InnerRadius + $X;
+                $Yc = sin(($i - 90) * PI / 180) * $InnerRadius + $Y;
+
+                if ($i < 90) {
+                    $Yc++;
+                }
+                if ($i > 180 && $i < 270) {
+                    $Xc++;
+                }
+                if ($i >= 270) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Plots[] = $Xc;
+                $Plots[] = $Yc;
+            }
+            $Boundaries[0]["X2"] = $Xc;
+            $Boundaries[0]["Y2"] = $Yc;
+
+            /* Draw the polygon */
+            $this->pChartObject->drawPolygon($Plots, $Settings);
+            if ($RecordImageMap && !$Shadow) {
+                $this->pChartObject->addToImageMap(
+                    "POLY",
+                    $this->arraySerialize($Plots),
+                    $this->pChartObject->toHTMLColor(
+                        $Palette[$ID]["R"],
+                        $Palette[$ID]["G"],
+                        $Palette[$ID]["B"]
+                    ),
+                    $Data["Series"][$Data["Abscissa"]]["Data"][$Key],
+                    $Value
+                );
+            }
+
+            /* Smooth the edges using AA */
+            foreach ($AAPixels as $Pos) {
+                $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $BorderColor);
+            }
+            $this->pChartObject->drawLine(
+                $Boundaries[0]["X1"],
+                $Boundaries[0]["Y1"],
+                $Boundaries[0]["X2"],
+                $Boundaries[0]["Y2"],
+                $BorderColor
+            );
+            $this->pChartObject->drawLine(
+                $Boundaries[1]["X1"],
+                $Boundaries[1]["Y1"],
+                $Boundaries[1]["X2"],
+                $Boundaries[1]["Y2"],
+                $BorderColor
+            );
+
+            if ($DrawLabels && !$Shadow) {
+                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
+                    $Settings = [
+                        "FillR" => $Palette[$ID]["R"],
+                        "FillG" => $Palette[$ID]["G"],
+                        "FillB" => $Palette[$ID]["B"],
+                        "Alpha" => $Palette[$ID]["Alpha"]
+                    ];
+                } else {
+                    $Settings = [
+                        "FillR" => $LabelR,
+                        "FillG" => $LabelG,
+                        "FillB" => $LabelB,
+                        "Alpha" => $LabelAlpha
+                    ];
+                }
+
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                $Xc = cos(($Angle - 90) * PI / 180) * $OuterRadius + $X;
+                $Yc = sin(($Angle - 90) * PI / 180) * $OuterRadius + $Y;
+
+                $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
+
+                if ($LabelStacked) {
+                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $OuterRadius);
+                } else {
+                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
+                }
+            }
+
+            $Offset = $Lasti;
+            $ID++;
+        }
+
+        if ($DrawLabels && $LabelStacked) {
+            $this->writeShiftedLabels();
+        }
+
+        if ($WriteValues && !$Shadow) {
+            $Step = 360 / (2 * PI * $OuterRadius);
+            $Offset = 0;
+            foreach ($Values as $Key => $Value) {
+                $EndAngle = $Offset + ($Value * $ScaleFactor);
+                if ($EndAngle > 360) {
+                    $EndAngle = 360;
+                }
+
+                $Angle = $Offset + ($Value * $ScaleFactor) / 2;
+                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
+                    $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $X;
+                    $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $Y;
+                    if ($Angle >= 0 && $Angle <= 90) {
+                        $Align = TEXT_ALIGN_BOTTOMLEFT;
+                    }
+                    if ($Angle > 90 && $Angle <= 180) {
+                        $Align = TEXT_ALIGN_TOPLEFT;
+                    }
+                    if ($Angle > 180 && $Angle <= 270) {
+                        $Align = TEXT_ALIGN_TOPRIGHT;
+                    }
+                    if ($Angle > 270) {
+                        $Align = TEXT_ALIGN_BOTTOMRIGHT;
+                    }
+                } else {
+                    $Xc = cos(($Angle - 90) * PI / 180)
+                        * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius)
+                        + $X
+                    ;
+                    $Yc = sin(($Angle - 90) * PI / 180)
+                        * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius)
+                        + $Y
+                    ;
+                    $Align = TEXT_ALIGN_MIDDLEMIDDLE;
+                }
+
+                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
+                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
+                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
+                    $Display = $Value . $ValueSuffix;
+                } else {
+                    $Display = "";
+                }
+                $this->pChartObject->drawText(
+                    $Xc,
+                    $Yc,
+                    $Display,
+                    ["Align" => $Align, "R" => $ValueR, "G" => $ValueG, "B" => $ValueB]
+                );
+                $Offset = $EndAngle;
+            }
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+
+        return PIE_RENDERED;
+    }
+
+    /**
+     * Draw a 3D ring chart
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int
+     */
+    public function draw3DRing($X, $Y, array $Format = [])
+    {
+        $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : 100;
+        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
+        $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30;
+        $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .6;
+        $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 10;
+        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 10;
+        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 10;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
+        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
+        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
+        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
+        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
+        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
+        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
+        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
+        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
+        $Cf = isset($Format["Cf"]) ? $Format["Cf"] : 20;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : PIE_VALUE_NATURAL;
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : $SliceHeight + 15;
+        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
+        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
+        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
+        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
+        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
+        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        /* Error correction for overlaying rounded corners */
+        if ($SkewFactor < .5) {
+            $SkewFactor = .5;
+        }
+
+        /* Data Processing */
+        $Data = $this->pDataObject->getData();
+        $Palette = $this->pDataObject->getPalette();
+
+        /* Do we have an abscissa serie defined? */
+        if ($Data["Abscissa"] == "") {
+            return PIE_NO_ABSCISSA;
+        }
+
+        /* Try to find the data serie */
+        $DataSerie = null;
+        foreach ($Data["Series"] as $SerieName => $SerieData) {
+            if ($SerieName != $Data["Abscissa"]) {
+                $DataSerie = $SerieName;
+            }
+        }
+
+        /* Do we have data to compute? */
+        if (!$DataSerie) {
+            return PIE_NO_DATASERIE;
+        }
+
+        /* Remove unused data */
+        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
+
+        /* Compute the pie sum */
+        $SerieSum = $this->pDataObject->getSum($DataSerie);
+
+        /* Do we have data to draw? */
+        if ($SerieSum == 0) {
+            return PIE_SUMISNULL;
+        }
+
+        /* Dump the real number of data to draw */
+        $Values = [];
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if ($Value != 0) {
+                $Values[] = $Value;
+            }
+        }
+
+        /* Compute the wasted angular space between series */
+        if (count($Values) == 1) {
+            $WastedAngular = 0;
+        } else {
+            $WastedAngular = count($Values) * $DataGapAngle;
+        }
+
+        /* Compute the scale */
+        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        if ($this->pChartObject->Shadow) {
+            $this->pChartObject->Shadow = false;
+        }
+
+        /* Draw the polygon ring elements */
+        $Offset = 360;
+        $ID = count($Values) - 1;
+        $Values = array_reverse($Values);
+        $Slice = 0;
+        $Slices = [];
+        $SliceColors = [];
+        $Visible = [];
+        $SliceAngle = [];
+        foreach ($Values as $Key => $Value) {
+            if (!isset($Palette[$ID]["R"])) {
+                $Color = $this->pChartObject->getRandomColor();
+                $Palette[$ID] = $Color;
+                $this->pDataObject->savePalette($ID, $Color);
+            }
+            $Settings = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"]
+            ];
+
+            $SliceColors[$Slice] = $Settings;
+
+            $StartAngle = $Offset;
+            $EndAngle = $Offset - ($Value * $ScaleFactor);
+            if ($EndAngle < 0) {
+                $EndAngle = 0;
+            }
+
+            if ($StartAngle > 180) {
+                $Visible[$Slice]["Start"] = true;
+            } else {
+                $Visible[$Slice]["Start"] = true;
+            }
+            if ($EndAngle < 180) {
+                $Visible[$Slice]["End"] = false;
+            } else {
+                $Visible[$Slice]["End"] = true;
+            }
+
+            $Step = (360 / (2 * PI * $OuterRadius)) / 2;
+            $OutX1 = VOID;
+            $OutY1 = VOID;
+            for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) * $SkewFactor + $Y;
+                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
+
+                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) * $SkewFactor + $Y;
+                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
+
+                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y;
+                $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
+
+                if ($OutX1 == VOID) {
+                    $OutX1 = $Xc;
+                    $OutY1 = $Yc;
+                }
+
+                if ($i < 90) {
+                    $Yc++;
+                }
+                if ($i > 90 && $i < 180) {
+                    $Xc++;
+                }
+                if ($i > 180 && $i < 270) {
+                    $Xc++;
+                }
+                if ($i >= 270) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Slices[$Slice]["BottomPoly"][] = floor($Xc);
+                $Slices[$Slice]["BottomPoly"][] = floor($Yc);
+                $Slices[$Slice]["TopPoly"][] = floor($Xc);
+                $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight;
+                $Slices[$Slice]["Angle"][] = $i;
+            }
+            $OutX2 = $Xc;
+            $OutY2 = $Yc;
+
+            $Slices[$Slice]["Angle"][] = VOID;
+            $Lasti = $i;
+
+            $Step = (360 / (2 * PI * $InnerRadius)) / 2;
+            $InX1 = VOID;
+            $InY1 = VOID;
+            for ($i = $EndAngle; $i <= $Offset; $i = $i + $Step) {
+                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) * $SkewFactor + $Y;
+                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
+
+                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) + $X;
+                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) * $SkewFactor + $Y;
+                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
+
+                if ($InX1 == VOID) {
+                    $InX1 = $Xc;
+                    $InY1 = $Yc;
+                }
+
+                if ($i < 90) {
+                    $Yc++;
+                }
+                if ($i > 90 && $i < 180) {
+                    $Xc++;
+                }
+                if ($i > 180 && $i < 270) {
+                    $Xc++;
+                }
+                if ($i >= 270) {
+                    $Xc++;
+                    $Yc++;
+                }
+
+                $Slices[$Slice]["BottomPoly"][] = floor($Xc);
+                $Slices[$Slice]["BottomPoly"][] = floor($Yc);
+                $Slices[$Slice]["TopPoly"][] = floor($Xc);
+                $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight;
+                $Slices[$Slice]["Angle"][] = $i;
+            }
+            $InX2 = $Xc;
+            $InY2 = $Yc;
+
+            $Slices[$Slice]["InX1"] = $InX1;
+            $Slices[$Slice]["InY1"] = $InY1;
+            $Slices[$Slice]["InX2"] = $InX2;
+            $Slices[$Slice]["InY2"] = $InY2;
+            $Slices[$Slice]["OutX1"] = $OutX1;
+            $Slices[$Slice]["OutY1"] = $OutY1;
+            $Slices[$Slice]["OutX2"] = $OutX2;
+            $Slices[$Slice]["OutY2"] = $OutY2;
+
+            $Offset = $Lasti - $DataGapAngle;
+            $ID--;
+            $Slice++;
+        }
+
+        /* Draw the bottom pie splice */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $this->pChartObject->drawPolygon($Plots["BottomPoly"], $Settings);
+
+            foreach ($Plots["AA"] as $Key => $Pos) {
+                $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $Settings);
+            }
+            $this->pChartObject->drawLine(
+                $Plots["InX1"],
+                $Plots["InY1"],
+                $Plots["OutX2"],
+                $Plots["OutY2"],
+                $Settings
+            );
+            $this->pChartObject->drawLine(
+                $Plots["InX2"],
+                $Plots["InY2"],
+                $Plots["OutX1"],
+                $Plots["OutY1"],
+                $Settings
+            );
+        }
+
+        $Slices = array_reverse($Slices);
+        $SliceColors = array_reverse($SliceColors);
+
+        /* Draw the vertical edges (semi-visible) */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf;
+            $Settings["G"] = $Settings["G"] + $Cf;
+            $Settings["B"] = $Settings["B"] + $Cf;
+
+            $StartAngle = $Plots["Angle"][0];
+            foreach ($Plots["Angle"] as $Key => $Angle) {
+                if ($Angle == VOID) {
+                    $EndAngle = $Plots["Angle"][$Key - 1];
+                }
+            }
+
+            if ($StartAngle >= 270 || $StartAngle <= 90) {
+                $this->pChartObject->drawLine(
+                    $Plots["OutX1"],
+                    $Plots["OutY1"],
+                    $Plots["OutX1"],
+                    $Plots["OutY1"] - $SliceHeight,
+                    $Settings
+                );
+            }
+            if ($StartAngle >= 270 || $StartAngle <= 90) {
+                $this->pChartObject->drawLine(
+                    $Plots["OutX2"],
+                    $Plots["OutY2"],
+                    $Plots["OutX2"],
+                    $Plots["OutY2"] - $SliceHeight,
+                    $Settings
+                );
+            }
+            $this->pChartObject->drawLine(
+                $Plots["InX1"],
+                $Plots["InY1"],
+                $Plots["InX1"],
+                $Plots["InY1"] - $SliceHeight,
+                $Settings
+            );
+            $this->pChartObject->drawLine(
+                $Plots["InX2"],
+                $Plots["InY2"],
+                $Plots["InX2"],
+                $Plots["InY2"] - $SliceHeight,
+                $Settings
+            );
+        }
+
+        /* Draw the inner vertical slices */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf;
+            $Settings["G"] = $Settings["G"] + $Cf;
+            $Settings["B"] = $Settings["B"] + $Cf;
+
+            $Outer = true;
+            $Inner = false;
+            $InnerPlotsA = [];
+            $InnerPlotsB = [];
+            foreach ($Plots["Angle"] as $ID => $Angle) {
+                if ($Angle == VOID) {
+                    $Outer = false;
+                    $Inner = true;
+                } elseif ($Inner && ($Angle < 90 || $Angle > 270) && isset($Plots["BottomPoly"][$ID * 2])) {
+                    $Xo = $Plots["BottomPoly"][$ID * 2];
+                    $Yo = $Plots["BottomPoly"][$ID * 2 + 1];
+
+                    $InnerPlotsA[] = $Xo;
+                    $InnerPlotsA[] = $Yo;
+                    $InnerPlotsB[] = $Xo;
+                    $InnerPlotsB[] = $Yo - $SliceHeight;
+                }
+            }
+
+            if (count($InnerPlotsA)) {
+                $InnerPlots = array_merge($InnerPlotsA, $this->arrayReverse($InnerPlotsB));
+                $this->pChartObject->drawPolygon($InnerPlots, $Settings);
+            }
+        }
+
+        /* Draw the splice top and left poly */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf * 1.5;
+            $Settings["G"] = $Settings["G"] + $Cf * 1.5;
+            $Settings["B"] = $Settings["B"] + $Cf * 1.5;
+
+            $StartAngle = $Plots["Angle"][0];
+            foreach ($Plots["Angle"] as $Key => $Angle) {
+                if ($Angle == VOID) {
+                    $EndAngle = $Plots["Angle"][$Key - 1];
+                }
+            }
+
+            if ($StartAngle < 180) {
+                $Points = [];
+                $Points[] = $Plots["InX2"];
+                $Points[] = $Plots["InY2"];
+                $Points[] = $Plots["InX2"];
+                $Points[] = $Plots["InY2"] - $SliceHeight;
+                $Points[] = $Plots["OutX1"];
+                $Points[] = $Plots["OutY1"] - $SliceHeight;
+                $Points[] = $Plots["OutX1"];
+                $Points[] = $Plots["OutY1"];
+
+                $this->pChartObject->drawPolygon($Points, $Settings);
+            }
+
+            if ($EndAngle > 180) {
+                $Points = [];
+                $Points[] = $Plots["InX1"];
+                $Points[] = $Plots["InY1"];
+                $Points[] = $Plots["InX1"];
+                $Points[] = $Plots["InY1"] - $SliceHeight;
+                $Points[] = $Plots["OutX2"];
+                $Points[] = $Plots["OutY2"] - $SliceHeight;
+                $Points[] = $Plots["OutX2"];
+                $Points[] = $Plots["OutY2"];
+
+                $this->pChartObject->drawPolygon($Points, $Settings);
+            }
+        }
+
+
+        /* Draw the vertical edges (visible) */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf;
+            $Settings["G"] = $Settings["G"] + $Cf;
+            $Settings["B"] = $Settings["B"] + $Cf;
+
+            $StartAngle = $Plots["Angle"][0];
+            foreach ($Plots["Angle"] as $Key => $Angle) {
+                if ($Angle == VOID) {
+                    $EndAngle = $Plots["Angle"][$Key - 1];
+                }
+            }
+
+            if ($StartAngle <= 270 && $StartAngle >= 90) {
+                $this->pChartObject->drawLine(
+                    $Plots["OutX1"],
+                    $Plots["OutY1"],
+                    $Plots["OutX1"],
+                    $Plots["OutY1"] - $SliceHeight,
+                    $Settings
+                );
+            }
+            if ($EndAngle <= 270 && $EndAngle >= 90) {
+                $this->pChartObject->drawLine(
+                    $Plots["OutX2"],
+                    $Plots["OutY2"],
+                    $Plots["OutX2"],
+                    $Plots["OutY2"] - $SliceHeight,
+                    $Settings
+                );
+            }
+        }
+
+
+        /* Draw the outer vertical slices */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf;
+            $Settings["G"] = $Settings["G"] + $Cf;
+            $Settings["B"] = $Settings["B"] + $Cf;
+
+            $Outer = true;
+            $Inner = false;
+            $OuterPlotsA = [];
+            $OuterPlotsB = [];
+            $InnerPlotsA = [];
+            $InnerPlotsB = [];
+            foreach ($Plots["Angle"] as $ID => $Angle) {
+                if ($Angle == VOID) {
+                    $Outer = false;
+                    $Inner = true;
+                } elseif ($Outer && ($Angle > 90 && $Angle < 270) && isset($Plots["BottomPoly"][$ID * 2])) {
+                    $Xo = $Plots["BottomPoly"][$ID * 2];
+                    $Yo = $Plots["BottomPoly"][$ID * 2 + 1];
+
+                    $OuterPlotsA[] = $Xo;
+                    $OuterPlotsA[] = $Yo;
+                    $OuterPlotsB[] = $Xo;
+                    $OuterPlotsB[] = $Yo - $SliceHeight;
+                }
+            }
+            if (count($OuterPlotsA)) {
+                $OuterPlots = array_merge($OuterPlotsA, $this->arrayReverse($OuterPlotsB));
+                $this->pChartObject->drawPolygon($OuterPlots, $Settings);
+            }
+        }
+
+        $Slices = array_reverse($Slices);
+        $SliceColors = array_reverse($SliceColors);
+
+        /* Draw the top pie splice */
+        foreach ($Slices as $SliceID => $Plots) {
+            $Settings = $SliceColors[$SliceID];
+            $Settings["NoBorder"] = true;
+            $Settings["R"] = $Settings["R"] + $Cf * 2;
+            $Settings["G"] = $Settings["G"] + $Cf * 2;
+            $Settings["B"] = $Settings["B"] + $Cf * 2;
+
+            $this->pChartObject->drawPolygon($Plots["TopPoly"], $Settings);
+
+            if ($RecordImageMap) {
+                $this->pChartObject->addToImageMap(
+                    "POLY",
+                    $this->arraySerialize($Plots["TopPoly"]),
+                    $this->pChartObject->toHTMLColor(
+                        $Settings["R"],
+                        $Settings["G"],
+                        $Settings["B"]
+                    ),
+                    $Data["Series"][$Data["Abscissa"]]["Data"][$SliceID],
+                    $Data["Series"][$DataSerie]["Data"][count($Slices) - $SliceID - 1]
+                );
+            }
+
+            foreach ($Plots["AA"] as $Key => $Pos) {
+                $this->pChartObject->drawAntialiasPixel(
+                    $Pos[0],
+                    $Pos[1] - $SliceHeight,
+                    $Settings
+                );
+            }
+            $this->pChartObject->drawLine(
+                $Plots["InX1"],
+                $Plots["InY1"] - $SliceHeight,
+                $Plots["OutX2"],
+                $Plots["OutY2"] - $SliceHeight,
+                $Settings
+            );
+            $this->pChartObject->drawLine(
+                $Plots["InX2"],
+                $Plots["InY2"] - $SliceHeight,
+                $Plots["OutX1"],
+                $Plots["OutY1"] - $SliceHeight,
+                $Settings
+            );
+        }
+
+        if ($DrawLabels) {
+            $Offset = 360;
+            foreach ($Values as $Key => $Value) {
+                $StartAngle = $Offset;
+                $EndAngle = $Offset - ($Value * $ScaleFactor);
+                if ($EndAngle < 0) {
+                    $EndAngle = 0;
+                }
+
+                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
+                    $Settings = [
+                        "FillR" => $Palette[$ID]["R"],
+                        "FillG" => $Palette[$ID]["G"],
+                        "FillB" => $Palette[$ID]["B"],
+                        "Alpha" => $Palette[$ID]["Alpha"]
+                    ];
+                } else {
+                    $Settings = [
+                        "FillR" => $LabelR,
+                        "FillG" => $LabelG,
+                        "FillB" => $LabelB,
+                        "Alpha" => $LabelAlpha
+                    ];
+                }
+
+                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
+                $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X;
+                $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y;
+
+                $Label = "";
+                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
+                    $Label = round((100 / $SerieSum) * $Value, $Precision) . "%";
+                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
+                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
+                }
+
+                if ($LabelStacked) {
+                    $this->writePieLabel(
+                        $Xc,
+                        $Yc - $SliceHeight,
+                        $Label,
+                        $Angle,
+                        $Settings,
+                        true,
+                        $X,
+                        $Y,
+                        $OuterRadius
+                    );
+                } else {
+                    $this->writePieLabel($Xc, $Yc - $SliceHeight, $Label, $Angle, $Settings, false);
+                }
+                $Offset = $EndAngle - $DataGapAngle;
+                $ID--;
+                $Slice++;
+            }
+        }
+        if ($DrawLabels && $LabelStacked) {
+            $this->writeShiftedLabels();
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+
+        return PIE_RENDERED;
+    }
+
+    /**
+     * Serialize an array
+     * @param array $Data
+     * @return string
+     */
+    public function arraySerialize(array $Data)
+    {
+        $Result = "";
+        foreach ($Data as $Value) {
+            if ($Result == "") {
+                $Result = floor($Value);
+            } else {
+                $Result = $Result . "," . floor($Value);
+            }
+        }
+
+        return $Result;
+    }
+
+    /**
+     * Reverse an array
+     * @param array $Plots
+     * @return array
+     */
+    public function arrayReverse(array $Plots)
+    {
+        $Result = [];
+
+        for ($i = count($Plots) - 1; $i >= 0; $i = $i - 2) {
+            $Result[] = $Plots[$i - 1];
+            $Result[] = $Plots[$i];
+        }
+
+        return $Result;
+    }
+
+    /**
+     * Remove unused series & values
+     * @param array $Data
+     * @param array $Palette
+     * @param string $DataSerie
+     * @param string $AbscissaSerie
+     * @return array
+     */
+    public function clean0Values(array $Data, array $Palette, $DataSerie, $AbscissaSerie)
+    {
+        $NewPalette = [];
+        $NewData = [];
+        $NewAbscissa = [];
+
+        /* Remove unused series */
+        foreach (array_keys($Data["Series"]) as $SerieName) {
+            if ($SerieName != $DataSerie && $SerieName != $AbscissaSerie) {
+                unset($Data["Series"][$SerieName]);
+            }
+        }
+
+        /* Remove null values */
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if ($Value != 0) {
+                $NewData[] = $Value;
+                $NewAbscissa[] = $Data["Series"][$AbscissaSerie]["Data"][$Key];
+                if (isset($Palette[$Key])) {
+                    $NewPalette[] = $Palette[$Key];
+                }
+            }
+        }
+        $Data["Series"][$DataSerie]["Data"] = $NewData;
+        $Data["Series"][$AbscissaSerie]["Data"] = $NewAbscissa;
+
+        return [$Data, $NewPalette];
+    }
+}

+ 1074 - 0
vendor/szymach/c-pchart/src/Chart/Radar.php

@@ -0,0 +1,1074 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+
+/**
+ * Radar - class to draw radar charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Radar
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * Draw a radar chart
+     * @param Image $Object
+     * @param Data $Values
+     * @param array $Format
+     */
+    public function drawRadar(Image $Object, Data $Values, array $Format = [])
+    {
+        $this->pChartObject = $Object;
+
+        $FixedMax = isset($Format["FixedMax"]) ? $Format["FixedMax"] : VOID;
+        $AxisR = isset($Format["AxisR"]) ? $Format["AxisR"] : 60;
+        $AxisG = isset($Format["AxisG"]) ? $Format["AxisG"] : 60;
+        $AxisB = isset($Format["AxisB"]) ? $Format["AxisB"] : 60;
+        $AxisAlpha = isset($Format["AxisAlpha"]) ? $Format["AxisAlpha"] : 50;
+        $AxisRotation = isset($Format["AxisRotation"]) ? $Format["AxisRotation"] : 0;
+        $DrawTicks = isset($Format["DrawTicks"]) ? $Format["DrawTicks"] : true;
+        $TicksLength = isset($Format["TicksLength"]) ? $Format["TicksLength"] : 2;
+        $DrawAxisValues = isset($Format["DrawAxisValues"]) ? $Format["DrawAxisValues"] : true;
+        $AxisBoxRounded = isset($Format["AxisBoxRounded"]) ? $Format["AxisBoxRounded"] : true;
+        $AxisFontName = isset($Format["AxisFontName"]) ? $Format["AxisFontName"] : $this->pChartObject->FontName;
+        $AxisFontSize = isset($Format["AxisFontSize"]) ? $Format["AxisFontSize"] : $this->pChartObject->FontSize;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : false;
+        $WriteValuesInBubble = isset($Format["WriteValuesInBubble"]) ? $Format["WriteValuesInBubble"] : true;
+        $ValueFontName = isset($Format["ValueFontName"]) ? $Format["ValueFontName"] : $this->pChartObject->FontName
+        ;
+        $ValueFontSize = isset($Format["ValueFontSize"]) ? $Format["ValueFontSize"] : $this->pChartObject->FontSize
+        ;
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 4;
+        $OuterBubbleRadius = isset($Format["OuterBubbleRadius"]) ? $Format["OuterBubbleRadius"] : 2;
+        $OuterBubbleR = isset($Format["OuterBubbleR"]) ? $Format["OuterBubbleR"] : VOID;
+        $OuterBubbleG = isset($Format["OuterBubbleG"]) ? $Format["OuterBubbleG"] : VOID;
+        $OuterBubbleB = isset($Format["OuterBubbleB"]) ? $Format["OuterBubbleB"] : VOID;
+        $OuterBubbleAlpha = isset($Format["OuterBubbleAlpha"]) ? $Format["OuterBubbleAlpha"] : 100;
+        $InnerBubbleR = isset($Format["InnerBubbleR"]) ? $Format["InnerBubbleR"] : 255;
+        $InnerBubbleG = isset($Format["InnerBubbleG"]) ? $Format["InnerBubbleG"] : 255;
+        $InnerBubbleB = isset($Format["InnerBubbleB"]) ? $Format["InnerBubbleB"] : 255;
+        $InnerBubbleAlpha = isset($Format["InnerBubbleAlpha"]) ? $Format["InnerBubbleAlpha"] : 100;
+        $DrawBackground = isset($Format["DrawBackground"]) ? $Format["DrawBackground"] : true;
+        $BackgroundR = isset($Format["BackgroundR"]) ? $Format["BackgroundR"] : 255;
+        $BackgroundG = isset($Format["BackgroundG"]) ? $Format["BackgroundG"] : 255;
+        $BackgroundB = isset($Format["BackgroundB"]) ? $Format["BackgroundB"] : 255;
+        $BackgroundAlpha = isset($Format["BackgroundAlpha"]) ? $Format["BackgroundAlpha"] : 50;
+        $BackgroundGradient = isset($Format["BackgroundGradient"]) ? $Format["BackgroundGradient"] : null;
+        $Layout = isset($Format["Layout"]) ? $Format["Layout"] : RADAR_LAYOUT_STAR;
+        $SegmentHeight = isset($Format["SegmentHeight"]) ? $Format["SegmentHeight"] : SEGMENT_HEIGHT_AUTO;
+        $Segments = isset($Format["Segments"]) ? $Format["Segments"] : 4;
+        $WriteLabels = isset($Format["WriteLabels"]) ? $Format["WriteLabels"] : true;
+        $SkipLabels = isset($Format["SkipLabels"]) ? $Format["SkipLabels"] : 1;
+        $LabelMiddle = isset($Format["LabelMiddle"]) ? $Format["LabelMiddle"] : false;
+        $LabelsBackground = isset($Format["LabelsBackground"]) ? $Format["LabelsBackground"] : true;
+        $LabelsBGR = isset($Format["LabelsBGR"]) ? $Format["LabelsBGR"] : 255;
+        $LabelsBGG = isset($Format["LabelsBGR"]) ? $Format["LabelsBGG"] : 255;
+        $LabelsBGB = isset($Format["LabelsBGR"]) ? $Format["LabelsBGB"] : 255;
+        $LabelsBGAlpha = isset($Format["LabelsBGAlpha"]) ? $Format["LabelsBGAlpha"] : 50;
+        $LabelPos = isset($Format["LabelPos"]) ? $Format["LabelPos"] : RADAR_LABELS_ROTATED;
+        $LabelPadding = isset($Format["LabelPadding"]) ? $Format["LabelPadding"] : 4;
+        $DrawPoints = isset($Format["DrawPoints"]) ? $Format["DrawPoints"] : true;
+        $PointRadius = isset($Format["PointRadius"]) ? $Format["PointRadius"] : 4;
+        $PointSurrounding = isset($Format["PointRadius"]) ? $Format["PointRadius"] : -30;
+        $DrawLines = isset($Format["DrawLines"]) ? $Format["DrawLines"] : true;
+        $LineLoopStart = isset($Format["LineLoopStart"]) ? $Format["LineLoopStart"] : true;
+        $DrawPoly = isset($Format["DrawPoly"]) ? $Format["DrawPoly"] : false;
+        $PolyAlpha = isset($Format["PolyAlpha"]) ? $Format["PolyAlpha"] : 40;
+        $FontSize = $Object->FontSize;
+        $X1 = $Object->GraphAreaX1;
+        $Y1 = $Object->GraphAreaY1;
+        $X2 = $Object->GraphAreaX2;
+        $Y2 = $Object->GraphAreaY2;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        /* Cancel default tick length if ticks not enabled */
+        if ($DrawTicks == false) {
+            $TicksLength = 0;
+        }
+
+        /* Data Processing */
+        $Data = $Values->getData();
+        $Palette = $Values->getPalette();
+
+        /* Catch the number of required axis */
+        $LabelSerie = $Data["Abscissa"];
+        if ($LabelSerie) {
+            $Points = count($Data["Series"][$LabelSerie]["Data"]);
+        } else {
+            $Points = 0;
+            foreach ($Data["Series"] as $SerieName => $DataArray) {
+                if (count($DataArray["Data"]) > $Points) {
+                    $Points = count($DataArray["Data"]);
+                }
+            }
+        }
+
+        /* Draw the axis */
+        $CenterX = ($X2 - $X1) / 2 + $X1;
+        $CenterY = ($Y2 - $Y1) / 2 + $Y1;
+
+        $EdgeHeight = min(($X2 - $X1) / 2, ($Y2 - $Y1) / 2);
+        if ($WriteLabels) {
+            $EdgeHeight = $EdgeHeight - $FontSize - $LabelPadding - $TicksLength;
+        }
+        /* Determine the scale if set to automatic */
+        if ($SegmentHeight == SEGMENT_HEIGHT_AUTO) {
+            if ($FixedMax != VOID) {
+                $Max = $FixedMax;
+            } else {
+                $Max = 0;
+                foreach ($Data["Series"] as $SerieName => $DataArray) {
+                    if ($SerieName != $LabelSerie) {
+                        if (max($DataArray["Data"]) > $Max) {
+                            $Max = max($DataArray["Data"]);
+                        }
+                    }
+                }
+            }
+            $MaxSegments = $EdgeHeight / 20;
+            $Scale = $Object->computeScale(0, $Max, $MaxSegments, [1, 2, 5]);
+
+            $Segments = $Scale["Rows"];
+            $SegmentHeight = $Scale["RowHeight"];
+        }
+
+        if ($LabelMiddle && $SkipLabels == 1) {
+            $Axisoffset = (360 / $Points) / 2;
+        } elseif ($LabelMiddle && $SkipLabels != 1) {
+            $Axisoffset = (360 / ($Points / $SkipLabels)) / 2;
+        } elseif (!$LabelMiddle) {
+            $Axisoffset = 0;
+        }
+
+        /* Background processing */
+        if ($DrawBackground) {
+            $RestoreShadow = $Object->Shadow;
+            $Object->Shadow = false;
+
+            if ($BackgroundGradient == null) {
+                if ($Layout == RADAR_LAYOUT_STAR) {
+                    $Color = [
+                        "R" => $BackgroundR,
+                        "G" => $BackgroundG,
+                        "B" => $BackgroundB,
+                        "Alpha" => $BackgroundAlpha
+                    ];
+                    $PointArray = [];
+                    for ($i = 0; $i <= 360; $i = $i + (360 / $Points)) {
+                        $PointArray[] = cos(deg2rad($i + $AxisRotation)) * $EdgeHeight + $CenterX;
+                        $PointArray[] = sin(deg2rad($i + $AxisRotation)) * $EdgeHeight + $CenterY;
+                    }
+                    $Object->drawPolygon($PointArray, $Color);
+                } elseif ($Layout == RADAR_LAYOUT_CIRCLE) {
+                    $Color = [
+                        "R" => $BackgroundR,
+                        "G" => $BackgroundG,
+                        "B" => $BackgroundB,
+                        "Alpha" => $BackgroundAlpha
+                    ];
+                    $Object->drawFilledCircle(
+                        $CenterX,
+                        $CenterY,
+                        $EdgeHeight,
+                        $Color
+                    );
+                }
+            } else {
+                $GradientROffset = ($BackgroundGradient["EndR"] - $BackgroundGradient["StartR"]) / $Segments;
+                $GradientGOffset = ($BackgroundGradient["EndG"] - $BackgroundGradient["StartG"]) / $Segments;
+                $GradientBOffset = ($BackgroundGradient["EndB"] - $BackgroundGradient["StartB"]) / $Segments;
+                $GradientAlphaOffset = ($BackgroundGradient["EndAlpha"] - $BackgroundGradient["StartAlpha"])
+                    / $Segments
+                ;
+
+                if ($Layout == RADAR_LAYOUT_STAR) {
+                    for ($j = $Segments; $j >= 1; $j--) {
+                        $Color = [
+                            "R" => $BackgroundGradient["StartR"] + $GradientROffset * $j,
+                            "G" => $BackgroundGradient["StartG"] + $GradientGOffset * $j,
+                            "B" => $BackgroundGradient["StartB"] + $GradientBOffset * $j,
+                            "Alpha" => $BackgroundGradient["StartAlpha"] + $GradientAlphaOffset * $j
+                        ];
+                        $PointArray = [];
+
+                        for ($i = 0; $i <= 360; $i = $i + (360 / $Points)) {
+                            $PointArray[] = cos(deg2rad($i + $AxisRotation))
+                                * ($EdgeHeight / $Segments) * $j + $CenterX
+                            ;
+                            $PointArray[] = sin(deg2rad($i + $AxisRotation))
+                                * ($EdgeHeight / $Segments) * $j + $CenterY
+                            ;
+                        }
+                        $Object->drawPolygon($PointArray, $Color);
+                    }
+                } elseif ($Layout == RADAR_LAYOUT_CIRCLE) {
+                    for ($j = $Segments; $j >= 1; $j--) {
+                        $Color = [
+                            "R" => $BackgroundGradient["StartR"] + $GradientROffset * $j,
+                            "G" => $BackgroundGradient["StartG"] + $GradientGOffset * $j,
+                            "B" => $BackgroundGradient["StartB"] + $GradientBOffset * $j,
+                            "Alpha" => $BackgroundGradient["StartAlpha"] + $GradientAlphaOffset * $j
+                        ];
+                        $Object->drawFilledCircle(
+                            $CenterX,
+                            $CenterY,
+                            ($EdgeHeight / $Segments) * $j,
+                            $Color
+                        );
+                    }
+                }
+            }
+            $Object->Shadow = $RestoreShadow;
+        }
+
+        /* Axis to axis lines */
+        $Color = ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha];
+        $ColorDotted = [
+            "R" => $AxisR,
+            "G" => $AxisG,
+            "B" => $AxisB,
+            "Alpha" => $AxisAlpha * .8,
+            "Ticks" => 2
+        ];
+        if ($Layout == RADAR_LAYOUT_STAR) {
+            for ($j = 1; $j <= $Segments; $j++) {
+                for ($i = 0; $i < 360; $i = $i + (360 / $Points)) {
+                    $EdgeX1 = cos(deg2rad($i + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterX;
+                    $EdgeY1 = sin(deg2rad($i + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterY;
+                    $EdgeX2 = cos(deg2rad($i + $AxisRotation + (360 / $Points)))
+                        * ($EdgeHeight / $Segments) * $j + $CenterX
+                    ;
+                    $EdgeY2 = sin(deg2rad($i + $AxisRotation + (360 / $Points)))
+                        * ($EdgeHeight / $Segments) * $j + $CenterY
+                    ;
+
+                    $Object->drawLine($EdgeX1, $EdgeY1, $EdgeX2, $EdgeY2, $Color);
+                }
+            }
+        } elseif ($Layout == RADAR_LAYOUT_CIRCLE) {
+            for ($j = 1; $j <= $Segments; $j++) {
+                $Radius = ($EdgeHeight / $Segments) * $j;
+                $Object->drawCircle($CenterX, $CenterY, $Radius, $Radius, $Color);
+            }
+        }
+
+        if ($DrawAxisValues) {
+            if ($LabelsBackground) {
+                $Options = [
+                    "DrawBox"  => true,
+                    "Align"  => TEXT_ALIGN_MIDDLEMIDDLE,
+                    "BoxR" => $LabelsBGR,
+                    "BoxG" => $LabelsBGG,
+                    "BoxB" => $LabelsBGB,
+                    "BoxAlpha" => $LabelsBGAlpha
+                ];
+            } else {
+                $Options = ["Align" => TEXT_ALIGN_MIDDLEMIDDLE];
+            }
+            if ($AxisBoxRounded) {
+                $Options["BoxRounded"] = true;
+            }
+
+            $Options["FontName"] = $AxisFontName;
+            $Options["FontSize"] = $AxisFontSize;
+
+            $Angle = 360 / ($Points * 2);
+            for ($j = 1; $j <= $Segments; $j++) {
+                $Label = $j * $SegmentHeight;
+
+                if ($Layout == RADAR_LAYOUT_CIRCLE) {
+                    $EdgeX1 = cos(deg2rad($Angle + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterX;
+                    $EdgeY1 = sin(deg2rad($Angle + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterY;
+                } elseif ($Layout == RADAR_LAYOUT_STAR) {
+                    $EdgeX1 = cos(deg2rad($AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterX;
+                    $EdgeY1 = sin(deg2rad($AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterY;
+                    $EdgeX2 = cos(deg2rad((360 / $Points) + $AxisRotation)) * ($EdgeHeight / $Segments)
+                        * $j + $CenterX
+                    ;
+                    $EdgeY2 = sin(deg2rad((360 / $Points) + $AxisRotation)) * ($EdgeHeight / $Segments)
+                        * $j + $CenterY
+                    ;
+
+                    $EdgeX1 = ($EdgeX2 - $EdgeX1) / 2 + $EdgeX1;
+                    $EdgeY1 = ($EdgeY2 - $EdgeY1) / 2 + $EdgeY1;
+                }
+
+                $Object->drawText($EdgeX1, $EdgeY1, $Label, $Options);
+            }
+        }
+
+        /* Axis lines */
+        $ID = 0;
+        for ($i = 0; $i < 360; $i = $i + (360 / $Points)) {
+            $EdgeX = cos(deg2rad($i + $AxisRotation)) * ($EdgeHeight + $TicksLength) + $CenterX;
+            $EdgeY = sin(deg2rad($i + $AxisRotation)) * ($EdgeHeight + $TicksLength) + $CenterY;
+
+            if ($ID % $SkipLabels == 0) {
+                $Object->drawLine($CenterX, $CenterY, $EdgeX, $EdgeY, $Color);
+            } else {
+                $Object->drawLine($CenterX, $CenterY, $EdgeX, $EdgeY, $ColorDotted);
+            }
+
+            if ($WriteLabels) {
+                $LabelX = cos(deg2rad($i + $AxisRotation + $Axisoffset))
+                    * ($EdgeHeight + $LabelPadding + $TicksLength) + $CenterX
+                ;
+                $LabelY = sin(deg2rad($i + $AxisRotation + $Axisoffset))
+                    * ($EdgeHeight + $LabelPadding + $TicksLength) + $CenterY
+                ;
+
+                if ($LabelSerie) {
+                    $Label = isset($Data["Series"][$LabelSerie]["Data"][$ID])
+                        ? $Data["Series"][$LabelSerie]["Data"][$ID] : ""
+                    ;
+                } else {
+                    $Label = $ID;
+                }
+
+                if ($ID % $SkipLabels == 0) {
+                    if ($LabelPos == RADAR_LABELS_ROTATED) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            [
+                                "Angle" => (360 - ($i + $AxisRotation + $Axisoffset)) - 90,
+                                "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                            ]
+                        );
+                    } else {
+                        if ((floor($LabelX) == floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                            );
+                        }
+                        if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_BOTTOMLEFT]
+                            );
+                        }
+                        if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) == floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                            );
+                        }
+                        if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_TOPLEFT]
+                            );
+                        }
+                        if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_BOTTOMRIGHT]
+                            );
+                        }
+                        if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) == floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_MIDDLERIGHT]
+                            );
+                        }
+                        if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_TOPRIGHT]
+                            );
+                        }
+                        if ((floor($LabelX) == floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                            $Object->drawText(
+                                $LabelX,
+                                $LabelY,
+                                $Label,
+                                ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                            );
+                        }
+                    }
+                }
+            }
+            $ID++;
+        }
+
+        /* Compute the plots position */
+        $ID = 0;
+        $Plot = [];
+        foreach ($Data["Series"] as $SerieName => $DataS) {
+            if ($SerieName != $LabelSerie) {
+                $Color = [
+                    "R" => $Palette[$ID]["R"],
+                    "G" => $Palette[$ID]["G"],
+                    "B" => $Palette[$ID]["B"],
+                    "Alpha" => $Palette[$ID]["Alpha"],
+                    "Surrounding" => $PointSurrounding
+                ];
+                foreach ($DataS["Data"] as $Key => $Value) {
+                    $Angle = (360 / $Points) * $Key;
+                    $Length = ($EdgeHeight / ($Segments * $SegmentHeight)) * $Value;
+
+                    $X = cos(deg2rad($Angle + $AxisRotation)) * $Length + $CenterX;
+                    $Y = sin(deg2rad($Angle + $AxisRotation)) * $Length + $CenterY;
+
+                    $Plot[$ID][] = [$X, $Y, $Value];
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "CIRCLE",
+                            sprintf("%s,%s,%s", floor($X), floor($Y), floor($PointRadius)),
+                            $this->pChartObject->toHTMLColor(
+                                $Palette[$ID]["R"],
+                                $Palette[$ID]["G"],
+                                $Palette[$ID]["B"]
+                            ),
+                            $DataS["Description"],
+                            $Data["Series"][$LabelSerie]["Data"][$Key] . " = " . $Value
+                        );
+                    }
+                }
+                $ID++;
+            }
+        }
+
+        /* Draw all that stuff! */
+        foreach ($Plot as $ID => $Points) {
+            $Color = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"],
+                "Surrounding" => $PointSurrounding
+            ];
+
+            /* Draw the polygons */
+            if ($DrawPoly) {
+                if ($PolyAlpha != null) {
+                    $Color = [
+                        "R" => $Palette[$ID]["R"],
+                        "G" => $Palette[$ID]["G"],
+                        "B" => $Palette[$ID]["B"],
+                        "Alpha" => $PolyAlpha,
+                        "Surrounding" => $PointSurrounding
+                    ];
+                }
+                $PointsArray = [];
+                for ($i = 0; $i < count($Points); $i++) {
+                    $PointsArray[] = $Points[$i][0];
+                    $PointsArray[] = $Points[$i][1];
+                }
+                $Object->drawPolygon($PointsArray, $Color);
+            }
+
+            $Color = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"],
+                "Surrounding" => $PointSurrounding
+            ];
+
+            /* Bubble and labels settings */
+            $TextSettings = [
+                "Align"  => TEXT_ALIGN_MIDDLEMIDDLE,
+                "FontName" => $ValueFontName,
+                "FontSize" => $ValueFontSize,
+                "R"  => $Palette[$ID]["R"],
+                "G"  => $Palette[$ID]["G"],
+                "B"  => $Palette[$ID]["B"]
+            ];
+            $InnerColor = [
+                "R" => $InnerBubbleR,
+                "G" => $InnerBubbleG,
+                "B" => $InnerBubbleB,
+                "Alpha" => $InnerBubbleAlpha
+            ];
+            if ($OuterBubbleR != VOID) {
+                $OuterColor = [
+                    "R" => $OuterBubbleR,
+                    "G" => $OuterBubbleG,
+                    "B" => $OuterBubbleB,
+                    "Alpha" => $OuterBubbleAlpha
+                ];
+            } else {
+                $OuterColor = [
+                    "R" => $Palette[$ID]["R"] + 20,
+                    "G" => $Palette[$ID]["G"] + 20,
+                    "B" => $Palette[$ID]["B"] + 20,
+                    "Alpha" => $Palette[$ID]["Alpha"]
+                ];
+            }
+            /* Loop to the starting points if asked */
+            if ($LineLoopStart && $DrawLines) {
+                $Object->drawLine(
+                    $Points[count($Points) - 1][0],
+                    $Points[count($Points) - 1][1],
+                    $Points[0][0],
+                    $Points[0][1],
+                    $Color
+                );
+            }
+
+            /* Draw the lines & points */
+            for ($i = 0; $i < count($Points); $i++) {
+                if ($DrawLines && $i < count($Points) - 1) {
+                    $Object->drawLine(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Points[$i + 1][0],
+                        $Points[$i + 1][1],
+                        $Color
+                    );
+                }
+                if ($DrawPoints) {
+                    $Object->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $PointRadius,
+                        $Color
+                    );
+                }
+                if ($WriteValuesInBubble && $WriteValues) {
+                    $TxtPos = $this->pChartObject->getTextBox(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $ValueFontName,
+                        $ValueFontSize,
+                        0,
+                        $Points[$i][2]
+                    );
+                    $Radius = floor(($TxtPos[1]["X"] - $TxtPos[0]["X"] + $ValuePadding * 2) / 2);
+
+                    $this->pChartObject->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Radius + $OuterBubbleRadius,
+                        $OuterColor
+                    );
+                    $this->pChartObject->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Radius,
+                        $InnerColor
+                    );
+                }
+
+                if ($WriteValues) {
+                    $this->pChartObject->drawText(
+                        $Points[$i][0] - 1,
+                        $Points[$i][1] - 1,
+                        $Points[$i][2],
+                        $TextSettings
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a radar chart
+     * @param Image $Object
+     * @param Data $Values
+     * @param array $Format
+     */
+    public function drawPolar(Image $Object, Data $Values, array $Format = [])
+    {
+        $this->pChartObject = $Object;
+
+        $FixedMax = isset($Format["FixedMax"]) ? $Format["FixedMax"] : VOID;
+        $AxisR = isset($Format["AxisR"]) ? $Format["AxisR"] : 60;
+        $AxisG = isset($Format["AxisG"]) ? $Format["AxisG"] : 60;
+        $AxisB = isset($Format["AxisB"]) ? $Format["AxisB"] : 60;
+        $AxisAlpha = isset($Format["AxisAlpha"]) ? $Format["AxisAlpha"] : 50;
+        $AxisRotation = isset($Format["AxisRotation"]) ? $Format["AxisRotation"] : -90;
+        $DrawTicks = isset($Format["DrawTicks"]) ? $Format["DrawTicks"] : true;
+        $TicksLength = isset($Format["TicksLength"]) ? $Format["TicksLength"] : 2;
+        $DrawAxisValues = isset($Format["DrawAxisValues"]) ? $Format["DrawAxisValues"] : true;
+        $AxisBoxRounded = isset($Format["AxisBoxRounded"]) ? $Format["AxisBoxRounded"] : true;
+        $AxisFontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName;
+        $AxisFontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize;
+        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : false;
+        $WriteValuesInBubble = isset($Format["WriteValuesInBubble"]) ? $Format["WriteValuesInBubble"] : true;
+        $ValueFontName = isset($Format["ValueFontName"])
+            ? $Format["ValueFontName"] : $this->pChartObject->FontName
+        ;
+        $ValueFontSize = isset($Format["ValueFontSize"])
+            ? $Format["ValueFontSize"] : $this->pChartObject->FontSize
+        ;
+        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 4;
+        $OuterBubbleRadius = isset($Format["OuterBubbleRadius"]) ? $Format["OuterBubbleRadius"] : 2;
+        $OuterBubbleR = isset($Format["OuterBubbleR"]) ? $Format["OuterBubbleR"] : VOID;
+        $OuterBubbleG = isset($Format["OuterBubbleG"]) ? $Format["OuterBubbleG"] : VOID;
+        $OuterBubbleB = isset($Format["OuterBubbleB"]) ? $Format["OuterBubbleB"] : VOID;
+        $OuterBubbleAlpha = isset($Format["OuterBubbleAlpha"]) ? $Format["OuterBubbleAlpha"] : 100;
+        $InnerBubbleR = isset($Format["InnerBubbleR"]) ? $Format["InnerBubbleR"] : 255;
+        $InnerBubbleG = isset($Format["InnerBubbleG"]) ? $Format["InnerBubbleG"] : 255;
+        $InnerBubbleB = isset($Format["InnerBubbleB"]) ? $Format["InnerBubbleB"] : 255;
+        $InnerBubbleAlpha = isset($Format["InnerBubbleAlpha"]) ? $Format["InnerBubbleAlpha"] : 100;
+        $DrawBackground = isset($Format["DrawBackground"]) ? $Format["DrawBackground"] : true;
+        $BackgroundR = isset($Format["BackgroundR"]) ? $Format["BackgroundR"] : 255;
+        $BackgroundG = isset($Format["BackgroundG"]) ? $Format["BackgroundG"] : 255;
+        $BackgroundB = isset($Format["BackgroundB"]) ? $Format["BackgroundB"] : 255;
+        $BackgroundAlpha = isset($Format["BackgroundAlpha"]) ? $Format["BackgroundAlpha"] : 50;
+        $BackgroundGradient = isset($Format["BackgroundGradient"]) ? $Format["BackgroundGradient"] : null;
+        $AxisSteps = isset($Format["AxisSteps"]) ? $Format["AxisSteps"] : 20;
+        $SegmentHeight = isset($Format["SegmentHeight"]) ? $Format["SegmentHeight"] : SEGMENT_HEIGHT_AUTO;
+        $Segments = isset($Format["Segments"]) ? $Format["Segments"] : 4;
+        $WriteLabels = isset($Format["WriteLabels"]) ? $Format["WriteLabels"] : true;
+        $LabelsBackground = isset($Format["LabelsBackground"]) ? $Format["LabelsBackground"] : true;
+        $LabelsBGR = isset($Format["LabelsBGR"]) ? $Format["LabelsBGR"] : 255;
+        $LabelsBGG = isset($Format["LabelsBGR"]) ? $Format["LabelsBGG"] : 255;
+        $LabelsBGB = isset($Format["LabelsBGR"]) ? $Format["LabelsBGB"] : 255;
+        $LabelsBGAlpha = isset($Format["LabelsBGAlpha"]) ? $Format["LabelsBGAlpha"] : 50;
+        $LabelPos = isset($Format["LabelPos"]) ? $Format["LabelPos"] : RADAR_LABELS_ROTATED;
+        $LabelPadding = isset($Format["LabelPadding"]) ? $Format["LabelPadding"] : 4;
+        $DrawPoints = isset($Format["DrawPoints"]) ? $Format["DrawPoints"] : true;
+        $PointRadius = isset($Format["PointRadius"]) ? $Format["PointRadius"] : 4;
+        $PointSurrounding = isset($Format["PointRadius"]) ? $Format["PointRadius"] : -30;
+        $DrawLines = isset($Format["DrawLines"]) ? $Format["DrawLines"] : true;
+        $LineLoopStart = isset($Format["LineLoopStart"]) ? $Format["LineLoopStart"] : false;
+        $DrawPoly = isset($Format["DrawPoly"]) ? $Format["DrawPoly"] : false;
+        $PolyAlpha = isset($Format["PolyAlpha"]) ? $Format["PolyAlpha"] : null;
+        $FontSize = $Object->FontSize;
+        $X1 = $Object->GraphAreaX1;
+        $Y1 = $Object->GraphAreaY1;
+        $X2 = $Object->GraphAreaX2;
+        $Y2 = $Object->GraphAreaY2;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        if ($AxisBoxRounded) {
+            $DrawAxisValues = true;
+        }
+
+        /* Cancel default tick length if ticks not enabled */
+        if ($DrawTicks == false) {
+            $TicksLength = 0;
+        }
+
+        /* Data Processing */
+        $Data = $Values->getData();
+        $Palette = $Values->getPalette();
+
+        /* Catch the number of required axis */
+        $LabelSerie = $Data["Abscissa"];
+        if ($LabelSerie) {
+            $Points = count($Data["Series"][$LabelSerie]["Data"]);
+        } else {
+            $Points = 0;
+            foreach ($Data["Series"] as $SerieName => $DataArray) {
+                if (count($DataArray["Data"]) > $Points) {
+                    $Points = count($DataArray["Data"]);
+                }
+            }
+        }
+
+        /* Draw the axis */
+        $CenterX = ($X2 - $X1) / 2 + $X1;
+        $CenterY = ($Y2 - $Y1) / 2 + $Y1;
+
+        $EdgeHeight = min(($X2 - $X1) / 2, ($Y2 - $Y1) / 2);
+        if ($WriteLabels) {
+            $EdgeHeight = $EdgeHeight - $FontSize - $LabelPadding - $TicksLength;
+        }
+
+        /* Determine the scale if set to automatic */
+        if ($SegmentHeight == SEGMENT_HEIGHT_AUTO) {
+            if ($FixedMax != VOID) {
+                $Max = $FixedMax;
+            } else {
+                $Max = 0;
+                foreach ($Data["Series"] as $SerieName => $DataArray) {
+                    if ($SerieName != $LabelSerie) {
+                        if (max($DataArray["Data"]) > $Max) {
+                            $Max = max($DataArray["Data"]);
+                        }
+                    }
+                }
+            }
+            $MaxSegments = $EdgeHeight / 20;
+            $Scale = $Object->computeScale(0, $Max, $MaxSegments, [1, 2, 5]);
+
+            $Segments = $Scale["Rows"];
+            $SegmentHeight = $Scale["RowHeight"];
+        }
+
+
+        /* Background processing */
+        if ($DrawBackground) {
+            $RestoreShadow = $Object->Shadow;
+            $Object->Shadow = false;
+
+            if ($BackgroundGradient == null) {
+                $Color = [
+                    "R" => $BackgroundR,
+                    "G" => $BackgroundG,
+                    "B" => $BackgroundB,
+                    "Alpha" => $BackgroundAlpha
+                ];
+                $Object->drawFilledCircle(
+                    $CenterX,
+                    $CenterY,
+                    $EdgeHeight,
+                    $Color
+                );
+            } else {
+                $GradientROffset = ($BackgroundGradient["EndR"] - $BackgroundGradient["StartR"]) / $Segments;
+                $GradientGOffset = ($BackgroundGradient["EndG"] - $BackgroundGradient["StartG"]) / $Segments;
+                $GradientBOffset = ($BackgroundGradient["EndB"] - $BackgroundGradient["StartB"]) / $Segments;
+                $GradientAlphaOffset = ($BackgroundGradient["EndAlpha"] - $BackgroundGradient["StartAlpha"])
+                    / $Segments
+                ;
+
+                for ($j = $Segments; $j >= 1; $j--) {
+                    $Color = [
+                        "R" => $BackgroundGradient["StartR"] + $GradientROffset * $j,
+                        "G" => $BackgroundGradient["StartG"] + $GradientGOffset * $j,
+                        "B" => $BackgroundGradient["StartB"] + $GradientBOffset * $j,
+                        "Alpha" => $BackgroundGradient["StartAlpha"] + $GradientAlphaOffset * $j
+                    ];
+                    $Object->drawFilledCircle(
+                        $CenterX,
+                        $CenterY,
+                        ($EdgeHeight / $Segments) * $j,
+                        $Color
+                    );
+                }
+            }
+            $Object->Shadow = $RestoreShadow;
+        }
+
+        /* Axis to axis lines */
+        $Color = ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha];
+        for ($j = 1; $j <= $Segments; $j++) {
+            $Radius = ($EdgeHeight / $Segments) * $j;
+            $Object->drawCircle($CenterX, $CenterY, $Radius, $Radius, $Color);
+        }
+
+        if ($DrawAxisValues) {
+            if ($LabelsBackground) {
+                $Options = [
+                    "DrawBox"  => true,
+                    "Align"  => TEXT_ALIGN_MIDDLEMIDDLE,
+                    "BoxR" => $LabelsBGR,
+                    "BoxG" => $LabelsBGG,
+                    "BoxB" => $LabelsBGB,
+                    "BoxAlpha" => $LabelsBGAlpha
+                ];
+            } else {
+                $Options = ["Align" => TEXT_ALIGN_MIDDLEMIDDLE];
+            }
+
+            if ($AxisBoxRounded) {
+                $Options["BoxRounded"] = true;
+            }
+
+            $Options["FontName"] = $AxisFontName;
+            $Options["FontSize"] = $AxisFontSize;
+
+            $Angle = 360 / ($Points * 2);
+            for ($j = 1; $j <= $Segments; $j++) {
+                $EdgeX1 = cos(deg2rad($Angle + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterX;
+                $EdgeY1 = sin(deg2rad($Angle + $AxisRotation)) * ($EdgeHeight / $Segments) * $j + $CenterY;
+                $Label = $j * $SegmentHeight;
+
+                $Object->drawText($EdgeX1, $EdgeY1, $Label, $Options);
+            }
+        }
+
+        /* Axis lines */
+        $ID = 0;
+        for ($i = 0; $i <= 359; $i = $i + $AxisSteps) {
+            $EdgeX = cos(deg2rad($i + $AxisRotation)) * ($EdgeHeight + $TicksLength) + $CenterX;
+            $EdgeY = sin(deg2rad($i + $AxisRotation)) * ($EdgeHeight + $TicksLength) + $CenterY;
+
+            $Object->drawLine($CenterX, $CenterY, $EdgeX, $EdgeY, $Color);
+
+            if ($WriteLabels) {
+                $LabelX = cos(deg2rad($i + $AxisRotation))
+                    * ($EdgeHeight + $LabelPadding + $TicksLength) + $CenterX
+                ;
+                $LabelY = sin(deg2rad($i + $AxisRotation))
+                    * ($EdgeHeight + $LabelPadding + $TicksLength) + $CenterY
+                ;
+                $Label = $i . "°";
+
+                if ($LabelPos == RADAR_LABELS_ROTATED) {
+                    $Object->drawText(
+                        $LabelX,
+                        $LabelY,
+                        $Label,
+                        ["Angle" => (360 - $i), "Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                    );
+                } else {
+                    if ((floor($LabelX) == floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                        );
+                    }
+                    if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_BOTTOMLEFT]
+                        );
+                    }
+                    if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) == floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                        );
+                    }
+                    if ((floor($LabelX) > floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_TOPLEFT]
+                        );
+                    }
+                    if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) < floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_BOTTOMRIGHT]
+                        );
+                    }
+                    if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) == floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_MIDDLERIGHT]
+                        );
+                    }
+                    if ((floor($LabelX) < floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_TOPRIGHT]
+                        );
+                    }
+                    if ((floor($LabelX) == floor($CenterX)) && (floor($LabelY) > floor($CenterY))) {
+                        $Object->drawText(
+                            $LabelX,
+                            $LabelY,
+                            $Label,
+                            ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                        );
+                    }
+                }
+            }
+            $ID++;
+        }
+
+        /* Compute the plots position */
+        $ID = 0;
+        $Plot = [];
+        foreach ($Data["Series"] as $SerieName => $DataSet) {
+            if ($SerieName != $LabelSerie) {
+                $Color = [
+                    "R" => $Palette[$ID]["R"],
+                    "G" => $Palette[$ID]["G"],
+                    "B" => $Palette[$ID]["B"],
+                    "Alpha" => $Palette[$ID]["Alpha"],
+                    "Surrounding" => $PointSurrounding
+                ];
+                foreach ($DataSet["Data"] as $Key => $Value) {
+                    $Angle = $Data["Series"][$LabelSerie]["Data"][$Key];
+                    $Length = ($EdgeHeight / ($Segments * $SegmentHeight)) * $Value;
+
+                    $X = cos(deg2rad($Angle + $AxisRotation)) * $Length + $CenterX;
+                    $Y = sin(deg2rad($Angle + $AxisRotation)) * $Length + $CenterY;
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "CIRCLE",
+                            sprintf("%s,%s,%s", floor($X), floor($Y), floor($PointRadius)),
+                            $this->pChartObject->toHTMLColor(
+                                $Palette[$ID]["R"],
+                                $Palette[$ID]["G"],
+                                $Palette[$ID]["B"]
+                            ),
+                            $DataSet["Description"],
+                            $Data["Series"][$LabelSerie]["Data"][$Key] . "&deg = " . $Value
+                        );
+                    }
+
+                    $Plot[$ID][] = [$X, $Y, $Value];
+                }
+                $ID++;
+            }
+        }
+
+        /* Draw all that stuff! */
+        foreach ($Plot as $ID => $Points) {
+            $Color = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"],
+                "Surrounding" => $PointSurrounding
+            ];
+
+            /* Draw the polygons */
+            if ($DrawPoly) {
+                if ($PolyAlpha != null) {
+                    $Color = [
+                        "R" => $Palette[$ID]["R"],
+                        "G" => $Palette[$ID]["G"],
+                        "B" => $Palette[$ID]["B"],
+                        "Alpha" => $PolyAlpha,
+                        "Surrounding" => $PointSurrounding
+                    ];
+                }
+                $PointsArray = [];
+                for ($i = 0; $i < count($Points); $i++) {
+                    $PointsArray[] = $Points[$i][0];
+                    $PointsArray[] = $Points[$i][1];
+                }
+
+                $Object->drawPolygon($PointsArray, $Color);
+            }
+
+            $Color = [
+                "R" => $Palette[$ID]["R"],
+                "G" => $Palette[$ID]["G"],
+                "B" => $Palette[$ID]["B"],
+                "Alpha" => $Palette[$ID]["Alpha"],
+                "Surrounding" => $PointSurrounding
+            ];
+
+            /* Bubble and labels settings */
+            $TextSettings = [
+                "Align"  => TEXT_ALIGN_MIDDLEMIDDLE,
+                "FontName" => $ValueFontName,
+                "FontSize" => $ValueFontSize,
+                "R"  => $Palette[$ID]["R"],
+                "G"  => $Palette[$ID]["G"],
+                "B"  => $Palette[$ID]["B"]
+            ];
+            $InnerColor = [
+                "R" => $InnerBubbleR,
+                "G" => $InnerBubbleG,
+                "B" => $InnerBubbleB,
+                "Alpha" => $InnerBubbleAlpha
+            ];
+            if ($OuterBubbleR != VOID) {
+                $OuterColor = [
+                    "R" => $OuterBubbleR,
+                    "G" => $OuterBubbleG,
+                    "B" => $OuterBubbleB,
+                    "Alpha" => $OuterBubbleAlpha
+                ];
+            } else {
+                $OuterColor = [
+                    "R" => $Palette[$ID]["R"] + 20,
+                    "G" => $Palette[$ID]["G"] + 20,
+                    "B" => $Palette[$ID]["B"] + 20,
+                    "Alpha" => $Palette[$ID]["Alpha"]
+                ];
+            }
+            /* Loop to the starting points if asked */
+            if ($LineLoopStart && $DrawLines) {
+                $Object->drawLine(
+                    $Points[count($Points) - 1][0],
+                    $Points[count($Points) - 1][1],
+                    $Points[0][0],
+                    $Points[0][1],
+                    $Color
+                );
+            }
+            /* Draw the lines & points */
+            for ($i = 0; $i < count($Points); $i++) {
+                if ($DrawLines && $i < count($Points) - 1) {
+                    $Object->drawLine(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Points[$i + 1][0],
+                        $Points[$i + 1][1],
+                        $Color
+                    );
+                }
+                if ($DrawPoints) {
+                    $Object->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $PointRadius,
+                        $Color
+                    );
+                }
+                if ($WriteValuesInBubble && $WriteValues) {
+                    $TxtPos = $this->pChartObject->getTextBox(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $ValueFontName,
+                        $ValueFontSize,
+                        0,
+                        $Points[$i][2]
+                    );
+                    $Radius = floor(($TxtPos[1]["X"] - $TxtPos[0]["X"] + $ValuePadding * 2) / 2);
+
+                    $this->pChartObject->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Radius + $OuterBubbleRadius,
+                        $OuterColor
+                    );
+                    $this->pChartObject->drawFilledCircle(
+                        $Points[$i][0],
+                        $Points[$i][1],
+                        $Radius,
+                        $InnerColor
+                    );
+                }
+
+                if ($WriteValues) {
+                    $this->pChartObject->drawText(
+                        $Points[$i][0] - 1,
+                        $Points[$i][1] - 1,
+                        $Points[$i][2],
+                        $TextSettings
+                    );
+                }
+            }
+        }
+    }
+}

+ 2255 - 0
vendor/szymach/c-pchart/src/Chart/Scatter.php

@@ -0,0 +1,2255 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+use Exception;
+
+/**
+ *  Scatter - class to draw scatter charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Scatter
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var Data
+     */
+    public $pDataObject;
+
+    /**
+     * @param Image $pChartObject
+     * @param Data $pDataObject
+     */
+    public function __construct(Image $pChartObject, Data $pDataObject)
+    {
+        $this->pChartObject = $pChartObject;
+        $this->pDataObject = $pDataObject;
+    }
+
+    /**
+     * Prepare the scale
+     * @param array $Format
+     * @return null|int
+     * @throws Exception
+     */
+    public function drawScatterScale(array $Format = [])
+    {
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : SCALE_MODE_FLOATING;
+        $Floating = isset($Format["Floating"]) ? $Format["Floating"] : false;
+        $XLabelsRotation = isset($Format["XLabelsRotation"]) ? $Format["XLabelsRotation"] : 90;
+        $MinDivHeight = isset($Format["MinDivHeight"]) ? $Format["MinDivHeight"] : 20;
+        $Factors = isset($Format["Factors"]) ? $Format["Factors"] : [1, 2, 5];
+        $ManualScale = isset($Format["ManualScale"])
+            ? $Format["ManualScale"] : ["0" => ["Min" => -100, "Max" => 100]]
+        ;
+        $XMargin = isset($Format["XMargin"]) ? $Format["XMargin"] : 0;
+        $YMargin = isset($Format["YMargin"]) ? $Format["YMargin"] : 0;
+        $ScaleSpacing = isset($Format["ScaleSpacing"]) ? $Format["ScaleSpacing"] : 15;
+        $InnerTickWidth = isset($Format["InnerTickWidth"]) ? $Format["InnerTickWidth"] : 2;
+        $OuterTickWidth = isset($Format["OuterTickWidth"]) ? $Format["OuterTickWidth"] : 2;
+        $DrawXLines = isset($Format["DrawXLines"]) ? $Format["DrawXLines"] : ALL;
+        $DrawYLines = isset($Format["DrawYLines"]) ? $Format["DrawYLines"] : ALL;
+        $GridTicks = isset($Format["GridTicks"]) ? $Format["GridTicks"] : 4;
+        $GridR = isset($Format["GridR"]) ? $Format["GridR"] : 255;
+        $GridG = isset($Format["GridG"]) ? $Format["GridG"] : 255;
+        $GridB = isset($Format["GridB"]) ? $Format["GridB"] : 255;
+        $GridAlpha = isset($Format["GridAlpha"]) ? $Format["GridAlpha"] : 40;
+        $AxisRo = isset($Format["AxisR"]) ? $Format["AxisR"] : 0;
+        $AxisGo = isset($Format["AxisG"]) ? $Format["AxisG"] : 0;
+        $AxisBo = isset($Format["AxisB"]) ? $Format["AxisB"] : 0;
+        $AxisAlpha = isset($Format["AxisAlpha"]) ? $Format["AxisAlpha"] : 100;
+        $TickRo = isset($Format["TickR"]) ? $Format["TickR"] : 0;
+        $TickGo = isset($Format["TickG"]) ? $Format["TickG"] : 0;
+        $TickBo = isset($Format["TickB"]) ? $Format["TickB"] : 0;
+        $TickAlpha = isset($Format["TickAlpha"]) ? $Format["TickAlpha"] : 100;
+        $DrawSubTicks = isset($Format["DrawSubTicks"]) ? $Format["DrawSubTicks"] : false;
+        $InnerSubTickWidth = isset($Format["InnerSubTickWidth"]) ? $Format["InnerSubTickWidth"] : 0;
+        $OuterSubTickWidth = isset($Format["OuterSubTickWidth"]) ? $Format["OuterSubTickWidth"] : 2;
+        $SubTickR = isset($Format["SubTickR"]) ? $Format["SubTickR"] : 255;
+        $SubTickG = isset($Format["SubTickG"]) ? $Format["SubTickG"] : 0;
+        $SubTickB = isset($Format["SubTickB"]) ? $Format["SubTickB"] : 0;
+        $SubTickAlpha = isset($Format["SubTickAlpha"]) ? $Format["SubTickAlpha"] : 100;
+        $XReleasePercent = isset($Format["XReleasePercent"]) ? $Format["XReleasePercent"] : 1;
+        $DrawArrows = isset($Format["DrawArrows"]) ? $Format["DrawArrows"] : false;
+        $ArrowSize = isset($Format["ArrowSize"]) ? $Format["ArrowSize"] : 8;
+        $CycleBackground = isset($Format["CycleBackground"]) ? $Format["CycleBackground"] : false;
+        $BackgroundR1 = isset($Format["BackgroundR1"]) ? $Format["BackgroundR1"] : 255;
+        $BackgroundG1 = isset($Format["BackgroundG1"]) ? $Format["BackgroundG1"] : 255;
+        $BackgroundB1 = isset($Format["BackgroundB1"]) ? $Format["BackgroundB1"] : 255;
+        $BackgroundAlpha1 = isset($Format["BackgroundAlpha1"]) ? $Format["BackgroundAlpha1"] : 10;
+        $BackgroundR2 = isset($Format["BackgroundR2"]) ? $Format["BackgroundR2"] : 230;
+        $BackgroundG2 = isset($Format["BackgroundG2"]) ? $Format["BackgroundG2"] : 230;
+        $BackgroundB2 = isset($Format["BackgroundB2"]) ? $Format["BackgroundB2"] : 230;
+        $BackgroundAlpha2 = isset($Format["BackgroundAlpha2"]) ? $Format["BackgroundAlpha2"] : 10;
+
+        /* Check if we have at least both one X and Y axis */
+        $GotXAxis = false;
+        $GotYAxis = false;
+        foreach ($this->pDataObject->Data["Axis"] as $AxisID => $AxisSettings) {
+            if ($AxisSettings["Identity"] == AXIS_X) {
+                $GotXAxis = true;
+            }
+            if ($AxisSettings["Identity"] == AXIS_Y) {
+                $GotYAxis = true;
+            }
+        }
+        if (!$GotXAxis) {
+            return SCATTER_MISSING_X_SERIE;
+        }
+        if (!$GotYAxis) {
+            return SCATTER_MISSING_Y_SERIE;
+        }
+
+        /* Skip a NOTICE event in case of an empty array */
+        if ($DrawYLines == NONE) {
+            $DrawYLines = ["zarma" => "31"];
+        }
+
+        $Data = $this->pDataObject->getData();
+
+        foreach ($Data["Axis"] as $AxisID => $AxisSettings) {
+            if ($AxisSettings["Identity"] == AXIS_X) {
+                $Width = $this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2;
+            } else {
+                $Width = $this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $YMargin * 2;
+            }
+
+            $AxisMin = ABSOLUTE_MAX;
+            $AxisMax = OUT_OF_SIGHT;
+            if ($Mode == SCALE_MODE_FLOATING) {
+                foreach ($Data["Series"] as $SerieID => $SerieParameter) {
+                    if ($SerieParameter["Axis"] == $AxisID
+                        && $Data["Series"][$SerieID]["isDrawable"]
+                    ) {
+                        $AxisMax = max($AxisMax, $Data["Series"][$SerieID]["Max"]);
+                        $AxisMin = min($AxisMin, $Data["Series"][$SerieID]["Min"]);
+                    }
+                }
+                $AutoMargin = (($AxisMax - $AxisMin) / 100) * $XReleasePercent;
+
+                $Data["Axis"][$AxisID]["Min"] = $AxisMin - $AutoMargin;
+                $Data["Axis"][$AxisID]["Max"] = $AxisMax + $AutoMargin;
+            } elseif ($Mode == SCALE_MODE_MANUAL) {
+                if (isset($ManualScale[$AxisID]["Min"])
+                    && isset($ManualScale[$AxisID]["Max"])
+                ) {
+                    $Data["Axis"][$AxisID]["Min"] = $ManualScale[$AxisID]["Min"];
+                    $Data["Axis"][$AxisID]["Max"] = $ManualScale[$AxisID]["Max"];
+                } else {
+                    throw new Exception("Manual scale boundaries not set.");
+                }
+            }
+
+            /* Full manual scale */
+            if (isset($ManualScale[$AxisID]["Rows"]) && isset($ManualScale[$AxisID]["RowHeight"])) {
+                $Scale = [
+                    "Rows" => $ManualScale[$AxisID]["Rows"],
+                    "RowHeight" => $ManualScale[$AxisID]["RowHeight"],
+                    "XMin" => $ManualScale[$AxisID]["Min"],
+                    "XMax" => $ManualScale[$AxisID]["Max"]
+                ];
+            } else {
+                $MaxDivs = floor($Width / $MinDivHeight);
+                $Scale = $this->pChartObject->computeScale(
+                    $Data["Axis"][$AxisID]["Min"],
+                    $Data["Axis"][$AxisID]["Max"],
+                    $MaxDivs,
+                    $Factors,
+                    $AxisID
+                );
+            }
+
+            $Data["Axis"][$AxisID]["Margin"] = $AxisSettings["Identity"] == AXIS_X ? $XMargin : $YMargin;
+            $Data["Axis"][$AxisID]["ScaleMin"] = $Scale["XMin"];
+            $Data["Axis"][$AxisID]["ScaleMax"] = $Scale["XMax"];
+            $Data["Axis"][$AxisID]["Rows"] = $Scale["Rows"];
+            $Data["Axis"][$AxisID]["RowHeight"] = $Scale["RowHeight"];
+
+            if (isset($Scale["Format"])) {
+                $Data["Axis"][$AxisID]["Format"] = $Scale["Format"];
+            }
+
+            if (!isset($Data["Axis"][$AxisID]["Display"])) {
+                $Data["Axis"][$AxisID]["Display"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Format"])) {
+                $Data["Axis"][$AxisID]["Format"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Unit"])) {
+                $Data["Axis"][$AxisID]["Unit"] = null;
+            }
+        }
+
+        /* Get the default font color */
+        $FontColorRo = $this->pChartObject->FontColorR;
+        $FontColorGo = $this->pChartObject->FontColorG;
+        $FontColorBo = $this->pChartObject->FontColorB;
+
+        /* Set the original boundaries */
+        $AxisPos["L"] = $this->pChartObject->GraphAreaX1;
+        $AxisPos["R"] = $this->pChartObject->GraphAreaX2;
+        $AxisPos["T"] = $this->pChartObject->GraphAreaY1;
+        $AxisPos["B"] = $this->pChartObject->GraphAreaY2;
+
+        foreach ($Data["Axis"] as $AxisID => $AxisSettings) {
+            if (isset($AxisSettings["Color"])) {
+                $AxisR = $AxisSettings["Color"]["R"];
+                $AxisG = $AxisSettings["Color"]["G"];
+                $AxisB = $AxisSettings["Color"]["B"];
+                $TickR = $AxisSettings["Color"]["R"];
+                $TickG = $AxisSettings["Color"]["G"];
+                $TickB = $AxisSettings["Color"]["B"];
+                $this->pChartObject->setFontProperties(
+                    [
+                        "R" => $AxisSettings["Color"]["R"],
+                        "G" => $AxisSettings["Color"]["G"],
+                        "B" => $AxisSettings["Color"]["B"]
+                    ]
+                );
+            } else {
+                $AxisR = $AxisRo;
+                $AxisG = $AxisGo;
+                $AxisB = $AxisBo;
+                $TickR = $TickRo;
+                $TickG = $TickGo;
+                $TickB = $TickBo;
+                $this->pChartObject->setFontProperties(
+                    ["R" => $FontColorRo, "G" => $FontColorGo, "B" => $FontColorBo]
+                );
+            }
+
+            if ($AxisSettings["Identity"] == AXIS_X) {
+                if ($AxisSettings["Position"] == AXIS_POSITION_BOTTOM) {
+                    if ($XLabelsRotation == 0) {
+                        $LabelAlign = TEXT_ALIGN_TOPMIDDLE;
+                        $LabelOffset = 2;
+                    }
+                    if ($XLabelsRotation > 0 && $XLabelsRotation < 190) {
+                        $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                        $LabelOffset = 5;
+                    }
+                    if ($XLabelsRotation == 180) {
+                        $LabelAlign = TEXT_ALIGN_BOTTOMMIDDLE;
+                        $LabelOffset = 5;
+                    }
+                    if ($XLabelsRotation > 180 && $XLabelsRotation < 360) {
+                        $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                        $LabelOffset = 2;
+                    }
+
+                    if ($Floating) {
+                        $FloatingOffset = $YMargin;
+                        $this->pChartObject->drawLine(
+                            $this->pChartObject->GraphAreaX1 + $AxisSettings["Margin"],
+                            $AxisPos["B"],
+                            $this->pChartObject->GraphAreaX2 - $AxisSettings["Margin"],
+                            $AxisPos["B"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    } else {
+                        $FloatingOffset = 0;
+                        $this->pChartObject->drawLine(
+                            $this->pChartObject->GraphAreaX1,
+                            $AxisPos["B"],
+                            $this->pChartObject->GraphAreaX2,
+                            $AxisPos["B"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    }
+
+                    if ($DrawArrows) {
+                        $this->pChartObject->drawArrow(
+                            $this->pChartObject->GraphAreaX2 - $AxisSettings["Margin"],
+                            $AxisPos["B"],
+                            $this->pChartObject->GraphAreaX2 + ($ArrowSize * 2),
+                            $AxisPos["B"],
+                            ["FillR" => $AxisR, "FillG" => $AxisG, "FillB" => $AxisB, "Size" => $ArrowSize]
+                        );
+                    }
+
+                    $Width = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1)
+                        - $AxisSettings["Margin"] * 2
+                    ;
+                    $Step = $Width / $AxisSettings["Rows"];
+                    $SubTicksSize = $Step / 2;
+                    $MaxBottom = $AxisPos["B"];
+                    $LastX = null;
+                    for ($i = 0; $i <= $AxisSettings["Rows"]; $i++) {
+                        $XPos = $this->pChartObject->GraphAreaX1 + $AxisSettings["Margin"] + $Step * $i;
+                        $YPos = $AxisPos["B"];
+                        $Value = $this->pChartObject->scaleFormat(
+                            $AxisSettings["ScaleMin"] + $AxisSettings["RowHeight"] * $i,
+                            $AxisSettings["Display"],
+                            $AxisSettings["Format"],
+                            $AxisSettings["Unit"]
+                        );
+
+                        if ($i % 2 == 1) {
+                            $BGColor = [
+                                "R" => $BackgroundR1,
+                                "G" => $BackgroundG1,
+                                "B" => $BackgroundB1,
+                                "Alpha" => $BackgroundAlpha1
+                            ];
+                        } else {
+                            $BGColor = [
+                                "R" => $BackgroundR2,
+                                "G" => $BackgroundG2,
+                                "B" => $BackgroundB2,
+                                "Alpha" => $BackgroundAlpha2
+                            ];
+                        }
+                        if ($LastX != null
+                            && $CycleBackground
+                            && ($DrawXLines == ALL || in_array($AxisID, $DrawXLines))
+                        ) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $LastX,
+                                $this->pChartObject->GraphAreaY1 + $FloatingOffset,
+                                $XPos,
+                                $this->pChartObject->GraphAreaY2 - $FloatingOffset,
+                                $BGColor
+                            );
+                        }
+
+                        if ($DrawXLines == ALL || in_array($AxisID, $DrawXLines)) {
+                            $this->pChartObject->drawLine(
+                                $XPos,
+                                $this->pChartObject->GraphAreaY1 + $FloatingOffset,
+                                $XPos,
+                                $this->pChartObject->GraphAreaY2 - $FloatingOffset,
+                                [
+                                    "R" => $GridR,
+                                    "G" => $GridG,
+                                    "B" => $GridB,
+                                    "Alpha" => $GridAlpha,
+                                    "Ticks" => $GridTicks
+                                ]
+                            );
+                        }
+                        if ($DrawSubTicks && $i != $AxisSettings["Rows"]) {
+                            $this->pChartObject->drawLine(
+                                $XPos + $SubTicksSize,
+                                $YPos - $InnerSubTickWidth,
+                                $XPos + $SubTicksSize,
+                                $YPos + $OuterSubTickWidth,
+                                [
+                                    "R" => $SubTickR,
+                                    "G" => $SubTickG,
+                                    "B" => $SubTickB,
+                                    "Alpha" => $SubTickAlpha
+                                ]
+                            );
+                        }
+                        $this->pChartObject->drawLine(
+                            $XPos,
+                            $YPos - $InnerTickWidth,
+                            $XPos,
+                            $YPos + $OuterTickWidth,
+                            ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                        );
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos + $OuterTickWidth + $LabelOffset,
+                            $Value,
+                            ["Angle" => $XLabelsRotation, "Align" => $LabelAlign]
+                        );
+                        $TxtBottom = $YPos + 2 + $OuterTickWidth + 2 + ($Bounds[0]["Y"] - $Bounds[2]["Y"]);
+                        $MaxBottom = max($MaxBottom, $TxtBottom);
+
+                        $LastX = $XPos;
+                    }
+
+                    if (isset($AxisSettings["Name"])) {
+                        $YPos = $MaxBottom + 2;
+                        $XPos = $this->pChartObject->GraphAreaX1
+                            + ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / 2
+                        ;
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos,
+                            $AxisSettings["Name"],
+                            ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                        );
+                        $MaxBottom = $Bounds[0]["Y"];
+
+                        $this->pDataObject->Data["GraphArea"]["Y2"] = $MaxBottom + $this->pChartObject->FontSize;
+                    }
+
+                    $AxisPos["B"] = $MaxBottom + $ScaleSpacing;
+                } elseif ($AxisSettings["Position"] == AXIS_POSITION_TOP) {
+                    if ($XLabelsRotation == 0) {
+                        $LabelAlign = TEXT_ALIGN_BOTTOMMIDDLE;
+                        $LabelOffset = 2;
+                    }
+                    if ($XLabelsRotation > 0 && $XLabelsRotation < 190) {
+                        $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                        $LabelOffset = 2;
+                    }
+                    if ($XLabelsRotation == 180) {
+                        $LabelAlign = TEXT_ALIGN_TOPMIDDLE;
+                        $LabelOffset = 5;
+                    }
+                    if ($XLabelsRotation > 180 && $XLabelsRotation < 360) {
+                        $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                        $LabelOffset = 5;
+                    }
+
+                    if ($Floating) {
+                        $FloatingOffset = $YMargin;
+                        $this->pChartObject->drawLine(
+                            $this->pChartObject->GraphAreaX1 + $AxisSettings["Margin"],
+                            $AxisPos["T"],
+                            $this->pChartObject->GraphAreaX2 - $AxisSettings["Margin"],
+                            $AxisPos["T"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    } else {
+                        $FloatingOffset = 0;
+                        $this->pChartObject->drawLine(
+                            $this->pChartObject->GraphAreaX1,
+                            $AxisPos["T"],
+                            $this->pChartObject->GraphAreaX2,
+                            $AxisPos["T"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    }
+
+                    if ($DrawArrows) {
+                        $this->pChartObject->drawArrow(
+                            $this->pChartObject->GraphAreaX2 - $AxisSettings["Margin"],
+                            $AxisPos["T"],
+                            $this->pChartObject->GraphAreaX2 + ($ArrowSize * 2),
+                            $AxisPos["T"],
+                            [
+                                "FillR" => $AxisR,
+                                "FillG" => $AxisG,
+                                "FillB" => $AxisB,
+                                "Size" => $ArrowSize
+                            ]
+                        );
+                    }
+
+                    $Width = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1)
+                        - $AxisSettings["Margin"] * 2
+                    ;
+                    $Step = $Width / $AxisSettings["Rows"];
+                    $SubTicksSize = $Step / 2;
+                    $MinTop = $AxisPos["T"];
+                    $LastX = null;
+                    for ($i = 0; $i <= $AxisSettings["Rows"]; $i++) {
+                        $XPos = $this->pChartObject->GraphAreaX1 + $AxisSettings["Margin"] + $Step * $i;
+                        $YPos = $AxisPos["T"];
+                        $Value = $this->pChartObject->scaleFormat(
+                            $AxisSettings["ScaleMin"] + $AxisSettings["RowHeight"] * $i,
+                            $AxisSettings["Display"],
+                            $AxisSettings["Format"],
+                            $AxisSettings["Unit"]
+                        );
+
+                        if ($i % 2 == 1) {
+                            $BGColor = [
+                                "R" => $BackgroundR1,
+                                "G" => $BackgroundG1,
+                                "B" => $BackgroundB1,
+                                "Alpha" => $BackgroundAlpha1
+                            ];
+                        } else {
+                            $BGColor = [
+                                "R" => $BackgroundR2,
+                                "G" => $BackgroundG2,
+                                "B" => $BackgroundB2,
+                                "Alpha" => $BackgroundAlpha2
+                            ];
+                        }
+                        if ($LastX != null
+                            && $CycleBackground
+                            && ($DrawXLines == ALL || in_array($AxisID, $DrawXLines))
+                        ) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $LastX,
+                                $this->pChartObject->GraphAreaY1 + $FloatingOffset,
+                                $XPos,
+                                $this->pChartObject->GraphAreaY2 - $FloatingOffset,
+                                $BGColor
+                            );
+                        }
+
+                        if ($DrawXLines == ALL || in_array($AxisID, $DrawXLines)) {
+                            $this->pChartObject->drawLine(
+                                $XPos,
+                                $this->pChartObject->GraphAreaY1 + $FloatingOffset,
+                                $XPos,
+                                $this->pChartObject->GraphAreaY2 - $FloatingOffset,
+                                [
+                                    "R" => $GridR,
+                                    "G" => $GridG,
+                                    "B" => $GridB,
+                                    "Alpha" => $GridAlpha,
+                                    "Ticks" => $GridTicks
+                                ]
+                            );
+                        }
+
+                        if ($DrawSubTicks && $i != $AxisSettings["Rows"]) {
+                            $this->pChartObject->drawLine(
+                                $XPos + $SubTicksSize,
+                                $YPos - $OuterSubTickWidth,
+                                $XPos + $SubTicksSize,
+                                $YPos + $InnerSubTickWidth,
+                                [
+                                    "R" => $SubTickR,
+                                    "G" => $SubTickG,
+                                    "B" => $SubTickB,
+                                    "Alpha" => $SubTickAlpha
+                                ]
+                            );
+                        }
+                        $this->pChartObject->drawLine(
+                            $XPos,
+                            $YPos - $OuterTickWidth,
+                            $XPos,
+                            $YPos + $InnerTickWidth,
+                            ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                        );
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos - $OuterTickWidth - $LabelOffset,
+                            $Value,
+                            ["Angle" => $XLabelsRotation, "Align" => $LabelAlign]
+                        );
+                        $TxtBox = $YPos - $OuterTickWidth - 4 - ($Bounds[0]["Y"] - $Bounds[2]["Y"]);
+                        $MinTop = min($MinTop, $TxtBox);
+
+                        $LastX = $XPos;
+                    }
+
+                    if (isset($AxisSettings["Name"])) {
+                        $YPos = $MinTop - 2;
+                        $XPos = $this->pChartObject->GraphAreaX1
+                            + ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / 2
+                        ;
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos,
+                            $AxisSettings["Name"],
+                            ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                        );
+                        $MinTop = $Bounds[2]["Y"];
+
+                        $this->pDataObject->Data["GraphArea"]["Y1"] = $MinTop;
+                    }
+
+                    $AxisPos["T"] = $MinTop - $ScaleSpacing;
+                }
+            } elseif ($AxisSettings["Identity"] == AXIS_Y) {
+                if ($AxisSettings["Position"] == AXIS_POSITION_LEFT) {
+                    if ($Floating) {
+                        $FloatingOffset = $XMargin;
+                        $this->pChartObject->drawLine(
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY1 + $AxisSettings["Margin"],
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY2 - $AxisSettings["Margin"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    } else {
+                        $FloatingOffset = 0;
+                        $this->pChartObject->drawLine(
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY1,
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY2,
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    }
+
+                    if ($DrawArrows) {
+                        $this->pChartObject->drawArrow(
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY1 + $AxisSettings["Margin"],
+                            $AxisPos["L"],
+                            $this->pChartObject->GraphAreaY1 - ($ArrowSize * 2),
+                            [
+                                "FillR" => $AxisR,
+                                "FillG" => $AxisG,
+                                "FillB" => $AxisB,
+                                "Size" => $ArrowSize
+                            ]
+                        );
+                    }
+
+                    $Height = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1)
+                        - $AxisSettings["Margin"] * 2
+                    ;
+                    $Step = $Height / $AxisSettings["Rows"];
+                    $SubTicksSize = $Step / 2;
+                    $MinLeft = $AxisPos["L"];
+                    $LastY = null;
+                    for ($i = 0; $i <= $AxisSettings["Rows"]; $i++) {
+                        $YPos = $this->pChartObject->GraphAreaY2 - $AxisSettings["Margin"] - $Step * $i;
+                        $XPos = $AxisPos["L"];
+                        $Value = $this->pChartObject->scaleFormat(
+                            $AxisSettings["ScaleMin"] + $AxisSettings["RowHeight"] * $i,
+                            $AxisSettings["Display"],
+                            $AxisSettings["Format"],
+                            $AxisSettings["Unit"]
+                        );
+
+                        if ($i % 2 == 1) {
+                            $BGColor = [
+                                "R" => $BackgroundR1,
+                                "G" => $BackgroundG1,
+                                "B" => $BackgroundB1,
+                                "Alpha" => $BackgroundAlpha1
+                            ];
+                        } else {
+                            $BGColor = [
+                                "R" => $BackgroundR2,
+                                "G" => $BackgroundG2,
+                                "B" => $BackgroundB2,
+                                "Alpha" => $BackgroundAlpha2
+                            ];
+                        }
+                        if ($LastY != null
+                            && $CycleBackground
+                            && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                        ) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $this->pChartObject->GraphAreaX1 + $FloatingOffset,
+                                $LastY,
+                                $this->pChartObject->GraphAreaX2 - $FloatingOffset,
+                                $YPos,
+                                $BGColor
+                            );
+                        }
+
+                        if (($YPos != $this->pChartObject->GraphAreaY1 && $YPos != $this->pChartObject->GraphAreaY2)
+                            && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                        ) {
+                            $this->pChartObject->drawLine(
+                                $this->pChartObject->GraphAreaX1 + $FloatingOffset,
+                                $YPos,
+                                $this->pChartObject->GraphAreaX2 - $FloatingOffset,
+                                $YPos,
+                                [
+                                    "R" => $GridR,
+                                    "G" => $GridG,
+                                    "B" => $GridB,
+                                    "Alpha" => $GridAlpha,
+                                    "Ticks" => $GridTicks
+                                ]
+                            );
+                        }
+
+                        if ($DrawSubTicks && $i != $AxisSettings["Rows"]) {
+                            $this->pChartObject->drawLine(
+                                $XPos - $OuterSubTickWidth,
+                                $YPos - $SubTicksSize,
+                                $XPos + $InnerSubTickWidth,
+                                $YPos - $SubTicksSize,
+                                [
+                                    "R" => $SubTickR,
+                                    "G" => $SubTickG,
+                                    "B" => $SubTickB,
+                                    "Alpha" => $SubTickAlpha
+                                ]
+                            );
+                        }
+                        $this->pChartObject->drawLine(
+                            $XPos - $OuterTickWidth,
+                            $YPos,
+                            $XPos + $InnerTickWidth,
+                            $YPos,
+                            ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                        );
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos - $OuterTickWidth - 2,
+                            $YPos,
+                            $Value,
+                            ["Align" => TEXT_ALIGN_MIDDLERIGHT]
+                        );
+                        $TxtLeft = $XPos - $OuterTickWidth - 2 - ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                        $MinLeft = min($MinLeft, $TxtLeft);
+
+                        $LastY = $YPos;
+                    }
+
+                    if (isset($AxisSettings["Name"])) {
+                        $XPos = $MinLeft - 2;
+                        $YPos = $this->pChartObject->GraphAreaY1
+                            + ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / 2
+                        ;
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos,
+                            $AxisSettings["Name"],
+                            ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 90]
+                        );
+                        $MinLeft = $Bounds[2]["X"];
+
+                        $this->pDataObject->Data["GraphArea"]["X1"] = $MinLeft;
+                    }
+
+                    $AxisPos["L"] = $MinLeft - $ScaleSpacing;
+                } elseif ($AxisSettings["Position"] == AXIS_POSITION_RIGHT) {
+                    if ($Floating) {
+                        $FloatingOffset = $XMargin;
+                        $this->pChartObject->drawLine(
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY1 + $AxisSettings["Margin"],
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY2 - $AxisSettings["Margin"],
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    } else {
+                        $FloatingOffset = 0;
+                        $this->pChartObject->drawLine(
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY1,
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY2,
+                            ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                        );
+                    }
+
+                    if ($DrawArrows) {
+                        $this->pChartObject->drawArrow(
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY1 + $AxisSettings["Margin"],
+                            $AxisPos["R"],
+                            $this->pChartObject->GraphAreaY1 - ($ArrowSize * 2),
+                            [
+                                "FillR" => $AxisR,
+                                "FillG" => $AxisG,
+                                "FillB" => $AxisB,
+                                "Size" => $ArrowSize
+                            ]
+                        );
+                    }
+
+                    $Height = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1)
+                        - $AxisSettings["Margin"] * 2
+                    ;
+                    $Step = $Height / $AxisSettings["Rows"];
+                    $SubTicksSize = $Step / 2;
+                    $MaxLeft = $AxisPos["R"];
+                    $LastY = null;
+                    for ($i = 0; $i <= $AxisSettings["Rows"]; $i++) {
+                        $YPos = $this->pChartObject->GraphAreaY2 - $AxisSettings["Margin"] - $Step * $i;
+                        $XPos = $AxisPos["R"];
+                        $Value = $this->pChartObject->scaleFormat(
+                            $AxisSettings["ScaleMin"] + $AxisSettings["RowHeight"] * $i,
+                            $AxisSettings["Display"],
+                            $AxisSettings["Format"],
+                            $AxisSettings["Unit"]
+                        );
+
+                        if ($i % 2 == 1) {
+                            $BGColor = [
+                                "R" => $BackgroundR1,
+                                "G" => $BackgroundG1,
+                                "B" => $BackgroundB1,
+                                "Alpha" => $BackgroundAlpha1
+                            ];
+                        } else {
+                            $BGColor = [
+                                "R" => $BackgroundR2,
+                                "G" => $BackgroundG2,
+                                "B" => $BackgroundB2,
+                                "Alpha" => $BackgroundAlpha2
+                            ];
+                        }
+                        if ($LastY != null
+                            && $CycleBackground
+                            && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                        ) {
+                            $this->pChartObject->drawFilledRectangle(
+                                $this->pChartObject->GraphAreaX1 + $FloatingOffset,
+                                $LastY,
+                                $this->pChartObject->GraphAreaX2 - $FloatingOffset,
+                                $YPos,
+                                $BGColor
+                            );
+                        }
+
+                        if (($YPos != $this->pChartObject->GraphAreaY1
+                            && $YPos != $this->pChartObject->GraphAreaY2)
+                            && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                        ) {
+                            $this->pChartObject->drawLine(
+                                $this->pChartObject->GraphAreaX1 + $FloatingOffset,
+                                $YPos,
+                                $this->pChartObject->GraphAreaX2 - $FloatingOffset,
+                                $YPos,
+                                [
+                                    "R" => $GridR,
+                                    "G" => $GridG,
+                                    "B" => $GridB,
+                                    "Alpha" => $GridAlpha,
+                                    "Ticks" => $GridTicks
+                                ]
+                            );
+                        }
+
+                        if ($DrawSubTicks && $i != $AxisSettings["Rows"]) {
+                            $this->pChartObject->drawLine(
+                                $XPos - $InnerSubTickWidth,
+                                $YPos - $SubTicksSize,
+                                $XPos + $OuterSubTickWidth,
+                                $YPos - $SubTicksSize,
+                                [
+                                    "R" => $SubTickR,
+                                    "G" => $SubTickG,
+                                    "B" => $SubTickB,
+                                    "Alpha" => $SubTickAlpha
+                                ]
+                            );
+                        }
+                        $this->pChartObject->drawLine(
+                            $XPos - $InnerTickWidth,
+                            $YPos,
+                            $XPos + $OuterTickWidth,
+                            $YPos,
+                            ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                        );
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos + $OuterTickWidth + 2,
+                            $YPos,
+                            $Value,
+                            ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                        );
+                        $TxtLeft = $XPos + $OuterTickWidth + 2 + ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                        $MaxLeft = max($MaxLeft, $TxtLeft);
+
+                        $LastY = $YPos;
+                    }
+
+                    if (isset($AxisSettings["Name"])) {
+                        $XPos = $MaxLeft + 6;
+                        $YPos = $this->pChartObject->GraphAreaY1
+                            + ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / 2
+                        ;
+                        $Bounds = $this->pChartObject->drawText(
+                            $XPos,
+                            $YPos,
+                            $AxisSettings["Name"],
+                            ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 270]
+                        );
+                        $MaxLeft = $Bounds[2]["X"];
+
+                        $this->pDataObject->Data["GraphArea"]["X2"] = $MaxLeft + $this->pChartObject->FontSize;
+                    }
+
+                    $AxisPos["R"] = $MaxLeft + $ScaleSpacing;
+                }
+            }
+        }
+
+        $this->pDataObject->saveAxisConfig($Data["Axis"]);
+    }
+
+    /**
+     * Draw a scatter plot chart
+     * @param array $Format
+     */
+    public function drawScatterPlotChart($Format = null)
+    {
+        $PlotSize = isset($Format["PlotSize"]) ? $Format["PlotSize"] : 3;
+        $PlotBorder = isset($Format["PlotBorder"]) ? $Format["PlotBorder"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 250;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 250;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 250;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 30;
+        $BorderSize = isset($Format["BorderSize"]) ? $Format["BorderSize"] : 1;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapTitle = isset($Format["ImageMapTitle"]) ? $Format["ImageMapTitle"] : null;
+
+        $Data = $this->pDataObject->getData();
+
+        $BorderColor = ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $BorderAlpha];
+
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                $SerieX = $Series["X"];
+                $SerieValuesX = $Data["Series"][$SerieX]["Data"];
+                $SerieXAxis = $Data["Series"][$SerieX]["Axis"];
+                $SerieY = $Series["Y"];
+                $SerieValuesY = $Data["Series"][$SerieY]["Data"];
+                $SerieYAxis = $Data["Series"][$SerieY]["Axis"];
+
+                if ($ImageMapTitle == null) {
+                    $Description = sprintf(
+                        "%s / %s",
+                        $Data["Series"][$Series["X"]]["Description"],
+                        $Data["Series"][$Series["Y"]]["Description"]
+                    );
+                } else {
+                    $Description = $ImageMapTitle;
+                }
+
+                if (isset($Series["Picture"]) && $Series["Picture"] != "") {
+                    $Picture = $Series["Picture"];
+                    list($PicWidth, $PicHeight, $PicType) = $this->pChartObject->getPicInfo($Picture);
+                } else {
+                    $Picture = null;
+                }
+
+                $PosArrayX = $this->getPosArray($SerieValuesX, $SerieXAxis);
+                if (!is_array($PosArrayX)) {
+                    $Value = $PosArrayX;
+                    $PosArrayX = [];
+                    $PosArrayX[0] = $Value;
+                }
+                $PosArrayY = $this->getPosArray($SerieValuesY, $SerieYAxis);
+                if (!is_array($PosArrayY)) {
+                    $Value = $PosArrayY;
+                    $PosArrayY = [];
+                    $PosArrayY[0] = $Value;
+                }
+
+                $Color = [
+                    "R" => $Series["Color"]["R"],
+                    "G" => $Series["Color"]["G"],
+                    "B" => $Series["Color"]["B"],
+                    "Alpha" => $Series["Color"]["Alpha"]
+                ];
+
+                foreach ($PosArrayX as $Key => $Value) {
+                    $X = $Value;
+                    $Y = $PosArrayY[$Key];
+
+                    if ($X != VOID && $Y != VOID) {
+                        $RealValue = sprintf(
+                            "%s / %s",
+                            round($Data["Series"][$Series["X"]]["Data"][$Key], 2),
+                            round($Data["Series"][$Series["Y"]]["Data"][$Key], 2)
+                        );
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "CIRCLE",
+                                sprintf(
+                                    "%s,%s,%s",
+                                    floor($X),
+                                    floor($Y),
+                                    floor($PlotSize + $BorderSize)
+                                ),
+                                $this->pChartObject->toHTMLColor(
+                                    $Series["Color"]["R"],
+                                    $Series["Color"]["G"],
+                                    $Series["Color"]["B"]
+                                ),
+                                $Description,
+                                $RealValue
+                            );
+                        }
+
+                        if (isset($Series["Shape"])) {
+                            $this->pChartObject->drawShape(
+                                $X,
+                                $Y,
+                                $Series["Shape"],
+                                $PlotSize,
+                                $PlotBorder,
+                                $BorderSize,
+                                $Series["Color"]["R"],
+                                $Series["Color"]["G"],
+                                $Series["Color"]["B"],
+                                $Series["Color"]["Alpha"],
+                                $BorderR,
+                                $BorderG,
+                                $BorderB,
+                                $BorderAlpha
+                            );
+                        } elseif ($Picture == null) {
+                            if ($PlotBorder) {
+                                $this->pChartObject->drawFilledCircle(
+                                    $X,
+                                    $Y,
+                                    $PlotSize + $BorderSize,
+                                    $BorderColor
+                                );
+                            }
+                            $this->pChartObject->drawFilledCircle($X, $Y, $PlotSize, $Color);
+                        } else {
+                            $this->pChartObject->drawFromPicture(
+                                $PicType,
+                                $Picture,
+                                $X - $PicWidth / 2,
+                                $Y - $PicHeight / 2
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a scatter line chart
+     * @param array $Format
+     */
+    public function drawScatterLineChart($Format = null)
+    {
+        $Data = $this->pDataObject->getData();
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapTitle = isset($Format["ImageMapTitle"]) ? $Format["ImageMapTitle"] : null;
+        $ImageMapPlotSize = isset($Format["ImageMapPlotSize"]) ? $Format["ImageMapPlotSize"] : 10;
+
+        /* Parse all the series to draw */
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                $SerieX = $Series["X"];
+                $SerieValuesX = $Data["Series"][$SerieX]["Data"];
+                $SerieXAxis = $Data["Series"][$SerieX]["Axis"];
+                $SerieY = $Series["Y"];
+                $SerieValuesY = $Data["Series"][$SerieY]["Data"];
+                $SerieYAxis = $Data["Series"][$SerieY]["Axis"];
+                $Ticks = $Series["Ticks"];
+                $Weight = $Series["Weight"];
+
+                if ($ImageMapTitle == null) {
+                    $Description = sprintf(
+                        "%s / %s",
+                        $Data["Series"][$Series["X"]]["Description"],
+                        $Data["Series"][$Series["Y"]]["Description"]
+                    );
+                } else {
+                    $Description = $ImageMapTitle;
+                }
+
+                $PosArrayX = $this->getPosArray($SerieValuesX, $SerieXAxis);
+                if (!is_array($PosArrayX)) {
+                    $Value = $PosArrayX;
+                    $PosArrayX = [];
+                    $PosArrayX[0] = $Value;
+                }
+                $PosArrayY = $this->getPosArray($SerieValuesY, $SerieYAxis);
+                if (!is_array($PosArrayY)) {
+                    $Value = $PosArrayY;
+                    $PosArrayY = [];
+                    $PosArrayY[0] = $Value;
+                }
+
+                $Color = [
+                    "R" => $Series["Color"]["R"],
+                    "G" => $Series["Color"]["G"],
+                    "B" => $Series["Color"]["B"],
+                    "Alpha" => $Series["Color"]["Alpha"]
+                ];
+                if ($Ticks != 0) {
+                    $Color["Ticks"] = $Ticks;
+                }
+                if ($Weight != 0) {
+                    $Color["Weight"] = $Weight;
+                }
+
+                $LastX = VOID;
+                $LastY = VOID;
+                foreach ($PosArrayX as $Key => $Value) {
+                    $X = $Value;
+                    $Y = $PosArrayY[$Key];
+
+                    if ($X != VOID && $Y != VOID) {
+                        $RealValue = sprintf(
+                            "%s / %s",
+                            round($Data["Series"][$Series["X"]]["Data"][$Key], 2),
+                            round($Data["Series"][$Series["Y"]]["Data"][$Key], 2)
+                        );
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "CIRCLE",
+                                sprintf("%s,%s,%s", floor($X), floor($Y), $ImageMapPlotSize),
+                                $this->pChartObject->toHTMLColor(
+                                    $Series["Color"]["R"],
+                                    $Series["Color"]["G"],
+                                    $Series["Color"]["B"]
+                                ),
+                                $Description,
+                                $RealValue
+                            );
+                        }
+                    }
+
+                    if ($X != VOID && $Y != VOID && $LastX != VOID && $LastY != VOID) {
+                        $this->pChartObject->drawLine($LastX, $LastY, $X, $Y, $Color);
+                    }
+                    $LastX = $X;
+                    $LastY = $Y;
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a scatter spline chart
+     * @param array $Format
+     */
+    public function drawScatterSplineChart(array $Format = [])
+    {
+        $Data = $this->pDataObject->getData();
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapTitle = isset($Format["ImageMapTitle"]) ? $Format["ImageMapTitle"] : null;
+        $ImageMapPlotSize = isset($Format["ImageMapPlotSize"]) ? $Format["ImageMapPlotSize"] : 10;
+
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                $SerieX = $Series["X"];
+                $SerieValuesX = $Data["Series"][$SerieX]["Data"];
+                $SerieXAxis = $Data["Series"][$SerieX]["Axis"];
+                $SerieY = $Series["Y"];
+                $SerieValuesY = $Data["Series"][$SerieY]["Data"];
+                $SerieYAxis = $Data["Series"][$SerieY]["Axis"];
+                $Ticks = $Series["Ticks"];
+                $Weight = $Series["Weight"];
+
+                if ($ImageMapTitle == null) {
+                    $Description = sprintf(
+                        "%s / %s",
+                        $Data["Series"][$Series["X"]]["Description"],
+                        $Data["Series"][$Series["Y"]]["Description"]
+                    );
+                } else {
+                    $Description = $ImageMapTitle;
+                }
+
+                $PosArrayX = $this->getPosArray($SerieValuesX, $SerieXAxis);
+                if (!is_array($PosArrayX)) {
+                    $Value = $PosArrayX;
+                    $PosArrayX = [];
+                    $PosArrayX[0] = $Value;
+                }
+                $PosArrayY = $this->getPosArray($SerieValuesY, $SerieYAxis);
+                if (!is_array($PosArrayY)) {
+                    $Value = $PosArrayY;
+                    $PosArrayY = [];
+                    $PosArrayY[0] = $Value;
+                }
+
+                $SplineSettings = [
+                    "R" => $Series["Color"]["R"],
+                    "G" => $Series["Color"]["G"],
+                    "B" => $Series["Color"]["B"],
+                    "Alpha" => $Series["Color"]["Alpha"]
+                ];
+                if ($Ticks != 0) {
+                    $SplineSettings["Ticks"] = $Ticks;
+                }
+                if ($Weight != 0) {
+                    $SplineSettings["Weight"] = $Weight;
+                }
+
+                $LastX = VOID;
+                $LastY = VOID;
+                $WayPoints = [];
+                $Forces = [];
+                foreach ($PosArrayX as $Key => $Value) {
+                    $X = $Value;
+                    $Y = $PosArrayY[$Key];
+                    $Force = $this->pChartObject->getLength($LastX, $LastY, $X, $Y) / 5;
+
+                    if ($X != VOID && $Y != VOID) {
+                        $RealValue = sprintf(
+                            "%s / %s",
+                            round($Data["Series"][$Series["X"]]["Data"][$Key], 2),
+                            round($Data["Series"][$Series["Y"]]["Data"][$Key], 2)
+                        );
+                        if ($RecordImageMap) {
+                            $this->pChartObject->addToImageMap(
+                                "CIRCLE",
+                                sprintf("%s,%s,%s", floor($X), floor($Y), $ImageMapPlotSize),
+                                $this->pChartObject->toHTMLColor(
+                                    $Series["Color"]["R"],
+                                    $Series["Color"]["G"],
+                                    $Series["Color"]["B"]
+                                ),
+                                $Description,
+                                $RealValue
+                            );
+                        }
+                    }
+
+                    if ($X != VOID && $Y != VOID) {
+                        $WayPoints[] = [$X, $Y];
+                        $Forces[] = $Force;
+                    }
+
+                    if ($Y == VOID || $X == VOID) {
+                        $SplineSettings["Forces"] = $Forces;
+                        $this->pChartObject->drawSpline($WayPoints, $SplineSettings);
+                        $WayPoints = [];
+                        $Forces = [];
+                    }
+
+                    $LastX = $X;
+                    $LastY = $Y;
+                }
+                $SplineSettings["Forces"] = $Forces;
+                $this->pChartObject->drawSpline($WayPoints, $SplineSettings);
+            }
+        }
+    }
+
+    /**
+     * Return the scaled plot position
+     * @param mixed $Values
+     * @param string $AxisID
+     * @return mixed
+     */
+    public function getPosArray($Values, $AxisID)
+    {
+        $Data = $this->pDataObject->getData();
+
+        if (!is_array($Values)) {
+            $Values = [$Values];
+        }
+
+        if ($Data["Axis"][$AxisID]["Identity"] == AXIS_X) {
+            $Height = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1)
+                - $Data["Axis"][$AxisID]["Margin"] * 2
+            ;
+            $ScaleHeight = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
+            $Step = $Height / $ScaleHeight;
+
+            $Result = [];
+            foreach ($Values as $Key => $Value) {
+                if ($Value == VOID) {
+                    $Result[] = VOID;
+                } else {
+                    $Result[] = $this->pChartObject->GraphAreaX1
+                        + $Data["Axis"][$AxisID]["Margin"]
+                        + ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
+                    ;
+                }
+            }
+        } else {
+            $Height = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1)
+                - $Data["Axis"][$AxisID]["Margin"] * 2
+            ;
+            $ScaleHeight = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
+            $Step = $Height / $ScaleHeight;
+
+            $Result = [];
+            foreach ($Values as $Key => $Value) {
+                if ($Value == VOID) {
+                    $Result[] = VOID;
+                } else {
+                    $Result[] = $this->pChartObject->GraphAreaY2
+                        - $Data["Axis"][$AxisID]["Margin"]
+                        - ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
+                    ;
+                }
+            }
+        }
+        return count($Result) == 1 ? reset($Result) : $Result;
+    }
+
+    /**
+     * Draw the legend of the active series
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     */
+    public function drawScatterLegend($X, $Y, array $Format = [])
+    {
+        $Family = isset($Format["Family"]) ? $Format["Family"] : LEGEND_FAMILY_BOX;
+        $FontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize;
+        $FontR = isset($Format["FontR"]) ? $Format["FontR"] : $this->pChartObject->FontColorR;
+        $FontG = isset($Format["FontG"]) ? $Format["FontG"] : $this->pChartObject->FontColorG;
+        $FontB = isset($Format["FontB"]) ? $Format["FontB"] : $this->pChartObject->FontColorB;
+        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 5;
+        $BoxHeight = isset($Format["BoxHeight"]) ? $Format["BoxHeight"] : 5;
+        $IconAreaWidth = isset($Format["IconAreaWidth"]) ? $Format["IconAreaWidth"] : $BoxWidth;
+        $IconAreaHeight = isset($Format["IconAreaHeight"]) ? $Format["IconAreaHeight"] : $BoxHeight;
+        $XSpacing = isset($Format["XSpacing"]) ? $Format["XSpacing"] : 5;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
+        $R = isset($Format["R"]) ? $Format["R"] : 200;
+        $G = isset($Format["G"]) ? $Format["G"] : 200;
+        $B = isset($Format["B"]) ? $Format["B"] : 200;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Style = isset($Format["Style"]) ? $Format["Style"] : LEGEND_ROUND;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        $Data = $this->pDataObject->getData();
+
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true && isset($Series["Picture"])) {
+                list($PicWidth, $PicHeight) = $this->pChartObject->getPicInfo($Series["Picture"]);
+                if ($IconAreaWidth < $PicWidth) {
+                    $IconAreaWidth = $PicWidth;
+                }
+                if ($IconAreaHeight < $PicHeight) {
+                    $IconAreaHeight = $PicHeight;
+                }
+            }
+        }
+
+        $YStep = max($this->pChartObject->FontSize, $IconAreaHeight) + 5;
+        $XStep = $XSpacing;
+
+        $Boundaries = [];
+        $Boundaries["L"] = $X;
+        $Boundaries["T"] = $Y;
+        $Boundaries["R"] = 0;
+        $Boundaries["B"] = 0;
+        $vY = $Y;
+        $vX = $X;
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                if ($Mode == LEGEND_VERTICAL) {
+                    $BoxArray = $this->pChartObject->getTextBox(
+                        $vX + $IconAreaWidth + 4,
+                        $vY + $IconAreaHeight / 2,
+                        $FontName,
+                        $FontSize,
+                        0,
+                        $Series["Description"]
+                    );
+
+                    if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                        $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                    }
+                    if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                        $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                    }
+                    if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                        $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                    }
+
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    $vY = $vY + max($this->pChartObject->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->pChartObject->getTextBox(
+                            $vX + $IconAreaWidth + 6,
+                            $Y + $IconAreaHeight / 2 + (($this->pChartObject->FontSize + 3) * $Key),
+                            $FontName,
+                            $FontSize,
+                            0,
+                            $Value
+                        );
+
+                        if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                            $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                        }
+                        if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                            $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                        }
+                        if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                            $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                        }
+
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+                    $vX = max($Width) + $XStep;
+                }
+            }
+        }
+        $vY = $vY - $YStep;
+        $vX = $vX - $XStep;
+
+        $TopOffset = $Y - $Boundaries["T"];
+        if ($Boundaries["B"] - ($vY + $IconAreaHeight) < $TopOffset) {
+            $Boundaries["B"] = $vY + $IconAreaHeight + $TopOffset;
+        }
+
+        if ($Style == LEGEND_ROUND) {
+            $this->pChartObject->drawRoundedFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        } elseif ($Style == LEGEND_BOX) {
+            $this->pChartObject->drawFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        }
+        $RestoreShadow = $this->pChartObject->Shadow;
+        $this->pChartObject->Shadow = false;
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                $R = $Series["Color"]["R"];
+                $G = $Series["Color"]["G"];
+                $B = $Series["Color"]["B"];
+                $Ticks = $Series["Ticks"];
+                $Weight = $Series["Weight"];
+
+                if (isset($Series["Picture"])) {
+                    $Picture = $Series["Picture"];
+                    list($PicWidth, $PicHeight) = $this->pChartObject->getPicInfo($Picture);
+                    $PicX = $X + $IconAreaWidth / 2;
+                    $PicY = $Y + $IconAreaHeight / 2;
+
+                    $this->pChartObject->drawFromPNG($PicX - $PicWidth / 2, $PicY - $PicHeight / 2, $Picture);
+                } else {
+                    if ($Family == LEGEND_FAMILY_BOX) {
+                        if ($BoxWidth != $IconAreaWidth) {
+                            $XOffset = floor(($IconAreaWidth - $BoxWidth) / 2);
+                        } else {
+                            $XOffset = 0;
+                        }
+                        if ($BoxHeight != $IconAreaHeight) {
+                            $YOffset = floor(($IconAreaHeight - $BoxHeight) / 2);
+                        } else {
+                            $YOffset = 0;
+                        }
+
+                        $this->pChartObject->drawFilledRectangle(
+                            $X + 1 + $XOffset,
+                            $Y + 1 + $YOffset,
+                            $X + $BoxWidth + $XOffset + 1,
+                            $Y + $BoxHeight + 1 + $YOffset,
+                            ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
+                        );
+                        $this->pChartObject->drawFilledRectangle(
+                            $X + $XOffset,
+                            $Y + $YOffset,
+                            $X + $BoxWidth + $XOffset,
+                            $Y + $BoxHeight + $YOffset,
+                            ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
+                        );
+                    } elseif ($Family == LEGEND_FAMILY_CIRCLE) {
+                        $this->pChartObject->drawFilledCircle(
+                            $X + 1 + $IconAreaWidth / 2,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            min($IconAreaHeight / 2, $IconAreaWidth / 2),
+                            ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
+                        );
+                        $this->pChartObject->drawFilledCircle(
+                            $X + $IconAreaWidth / 2,
+                            $Y + $IconAreaHeight / 2,
+                            min($IconAreaHeight / 2, $IconAreaWidth / 2),
+                            ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
+                        );
+                    } elseif ($Family == LEGEND_FAMILY_LINE) {
+                        $this->pChartObject->drawLine(
+                            $X + 1,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            $X + 1 + $IconAreaWidth,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            [
+                                "R" => 0,
+                                "G" => 0,
+                                "B" => 0,
+                                "Alpha" => 20,
+                                "Ticks" => $Ticks,
+                                "Weight" => $Weight
+                            ]
+                        );
+                        $this->pChartObject->drawLine(
+                            $X,
+                            $Y + $IconAreaHeight / 2,
+                            $X + $IconAreaWidth,
+                            $Y + $IconAreaHeight / 2,
+                            ["R" => $R, "G" => $G, "B" => $B, "Ticks" => $Ticks, "Weight" => $Weight]
+                        );
+                    }
+                }
+
+                if ($Mode == LEGEND_VERTICAL) {
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    foreach ($Lines as $Key => $Value) {
+                        $this->pChartObject->drawText(
+                            $X + $IconAreaWidth + 4,
+                            $Y + $IconAreaHeight / 2 + (($this->pChartObject->FontSize + 3) * $Key),
+                            $Value,
+                            [
+                                "R" => $FontR,
+                                "G" => $FontG,
+                                "B" => $FontB,
+                                "Align" => TEXT_ALIGN_MIDDLELEFT
+                            ]
+                        );
+                    }
+                    $Y = $Y + max($this->pChartObject->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->pChartObject->drawText(
+                            $X + $IconAreaWidth + 4,
+                            $Y + $IconAreaHeight / 2 + (($this->pChartObject->FontSize + 3) * $Key),
+                            $Value,
+                            [
+                                "R" => $FontR,
+                                "G" => $FontG,
+                                "B" => $FontB,
+                                "Align" => TEXT_ALIGN_MIDDLELEFT
+                            ]
+                        );
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+                    $X = max($Width) + 2 + $XStep;
+                }
+            }
+        }
+
+        $this->pChartObject->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Get the legend box size
+     * @param array $Format
+     * @return array
+     */
+    public function getScatterLegendSize(array $Format = [])
+    {
+        $FontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize;
+        $BoxSize = isset($Format["BoxSize"]) ? $Format["BoxSize"] : 5;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
+        $XSpacing = isset($Format["XSpacing"]) ? $Format["XSpacing"] : 5;
+
+        $YStep = max($this->pChartObject->FontSize, $BoxSize) + 5;
+        $XStep = $BoxSize + 5;
+
+        $X = 100;
+        $Y = 100;
+
+        $Data = $this->pDataObject->getData();
+
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true && isset($Series["Picture"])) {
+                list($PicWidth, $PicHeight) = $this->pChartObject->getPicInfo($Series["Picture"]);
+                if ($IconAreaWidth < $PicWidth) {
+                    $IconAreaWidth = $PicWidth;
+                }
+                if ($IconAreaHeight < $PicHeight) {
+                    $IconAreaHeight = $PicHeight;
+                }
+            }
+        }
+
+        $YStep = max($this->pChartObject->FontSize, $IconAreaHeight) + 5;
+        $XStep = $XSpacing;
+
+        $Boundaries = [];
+        $Boundaries["L"] = $X;
+        $Boundaries["T"] = $Y;
+        $Boundaries["R"] = 0;
+        $Boundaries["B"] = 0;
+        $vY = $Y;
+        $vX = $X;
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                if ($Mode == LEGEND_VERTICAL) {
+                    $BoxArray = $this->pChartObject->getTextBox(
+                        $vX + $IconAreaWidth + 4,
+                        $vY + $IconAreaHeight / 2,
+                        $FontName,
+                        $FontSize,
+                        0,
+                        $Series["Description"]
+                    );
+
+                    if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                        $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                    }
+                    if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                        $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                    }
+                    if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                        $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                    }
+
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    $vY = $vY + max($this->pChartObject->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Series["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->pChartObject->getTextBox(
+                            $vX + $IconAreaWidth + 6,
+                            $Y + $IconAreaHeight / 2 + (($this->pChartObject->FontSize + 3) * $Key),
+                            $FontName,
+                            $FontSize,
+                            0,
+                            $Value
+                        );
+
+                        if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                            $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                        }
+                        if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                            $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                        }
+                        if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                            $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                        }
+
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+                    $vX = max($Width) + $XStep;
+                }
+            }
+        }
+        $vY = $vY - $YStep;
+        $vX = $vX - $XStep;
+
+        $TopOffset = $Y - $Boundaries["T"];
+        if ($Boundaries["B"] - ($vY + $BoxSize) < $TopOffset) {
+            $Boundaries["B"] = $vY + $BoxSize + $TopOffset;
+        }
+
+        $Width = ($Boundaries["R"] + $Margin) - ($Boundaries["L"] - $Margin);
+        $Height = ($Boundaries["B"] + $Margin) - ($Boundaries["T"] - $Margin);
+
+        return ["Width" => $Width, "Height" => $Height];
+    }
+
+    /**
+     * Draw the line of best fit
+     * @param array $Format
+     */
+    public function drawScatterBestFit(array $Format = [])
+    {
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 0;
+
+        $Data = $this->pDataObject->getData();
+
+        foreach ($Data["ScatterSeries"] as $Key => $Series) {
+            if ($Series["isDrawable"] == true) {
+                $SerieX = $Series["X"];
+                $SerieXAxis = $Data["Series"][$SerieX]["Axis"];
+                $SerieY = $Series["Y"];
+                $SerieYAxis = $Data["Series"][$SerieY]["Axis"];
+
+                $Color = [
+                    "R" => $Series["Color"]["R"],
+                    "G" => $Series["Color"]["G"],
+                    "B" => $Series["Color"]["B"],
+                    "Alpha" => $Series["Color"]["Alpha"]
+                ];
+                $Color["Ticks"] = $Ticks;
+
+                $PosArrayX = $Data["Series"][$Series["X"]]["Data"];
+                $PosArrayY = $Data["Series"][$Series["Y"]]["Data"];
+
+                $Sxy = 0;
+                $Sx = 0;
+                $Sy = 0;
+                $Sxx = 0;
+                foreach ($PosArrayX as $Key => $Value) {
+                    $X = $Value;
+                    $Y = $PosArrayY[$Key];
+
+                    $Sxy = $Sxy + $X * $Y;
+                    $Sx = $Sx + $X;
+                    $Sy = $Sy + $Y;
+                    $Sxx = $Sxx + $X * $X;
+                }
+
+                $n = count($PosArrayX);
+
+                if ((($n * $Sxx) == ($Sx * $Sx))) {
+                    $X1 = $this->getPosArray($Data["Axis"][$SerieXAxis]["ScaleMin"], $SerieXAxis);
+                    $X2 = $X1;
+                    $Y1 = $this->pChartObject->GraphAreaY1;
+                    $Y2 = $this->pChartObject->GraphAreaY2;
+                } else {
+                    $M = (($n * $Sxy) - ($Sx * $Sy)) / (($n * $Sxx) - ($Sx * $Sx));
+                    $B = (($Sy) - ($M * $Sx)) / ($n);
+
+                    $X1 = $this->getPosArray($Data["Axis"][$SerieXAxis]["ScaleMin"], $SerieXAxis);
+                    $Y1 = $this->getPosArray($M * $Data["Axis"][$SerieXAxis]["ScaleMin"] + $B, $SerieYAxis);
+                    $X2 = $this->getPosArray($Data["Axis"][$SerieXAxis]["ScaleMax"], $SerieXAxis);
+                    $Y2 = $this->getPosArray($M * $Data["Axis"][$SerieXAxis]["ScaleMax"] + $B, $SerieYAxis);
+
+                    $RealM = -($Y2 - $Y1) / ($X2 - $X1);
+
+                    if ($Y1 < $this->pChartObject->GraphAreaY1) {
+                        $X1 = $X1 + ($this->pChartObject->GraphAreaY1 - $Y1 / $RealM);
+                        $Y1 = $this->pChartObject->GraphAreaY1;
+                    }
+                    if ($Y1 > $this->pChartObject->GraphAreaY2) {
+                        $X1 = $X1 + ($Y1 - $this->pChartObject->GraphAreaY2) / $RealM;
+                        $Y1 = $this->pChartObject->GraphAreaY2;
+                    }
+                    if ($Y2 < $this->pChartObject->GraphAreaY1) {
+                        $X2 = $X2 - ($this->pChartObject->GraphAreaY1 - $Y2) / $RealM;
+                        $Y2 = $this->pChartObject->GraphAreaY1;
+                    }
+                    if ($Y2 > $this->pChartObject->GraphAreaY2) {
+                        $X2 = $X2 - ($Y2 - $this->pChartObject->GraphAreaY2) / $RealM;
+                        $Y2 = $this->pChartObject->GraphAreaY2;
+                    }
+                }
+
+                $this->pChartObject->drawLine($X1, $Y1, $X2, $Y2, $Color);
+            }
+        }
+    }
+
+    /**
+     *
+     * @param string $ScatterSerieID
+     * @param mixed $Points
+     * @param array $Format
+     * @return null|int
+     */
+    public function writeScatterLabel($ScatterSerieID, $Points, array $Format = [])
+    {
+        $DrawPoint = isset($Format["DrawPoint"]) ? $Format["DrawPoint"] : LABEL_POINT_BOX;
+        $Decimals = isset($Format["Decimals"]) ? $Format["Decimals"] : null;
+
+        $Data = $this->pDataObject->getData();
+
+        if (!is_array($Points)) {
+            $Points = [$Points];
+        }
+
+        if (!isset($Data["ScatterSeries"][$ScatterSerieID])) {
+            return 0;
+        }
+        $Series = $Data["ScatterSeries"][$ScatterSerieID];
+
+        $SerieX = $Series["X"];
+        $SerieValuesX = $Data["Series"][$SerieX]["Data"];
+        $SerieXAxis = $Data["Series"][$SerieX]["Axis"];
+
+        $SerieY = $Series["Y"];
+        $SerieValuesY = $Data["Series"][$SerieY]["Data"];
+        $SerieYAxis = $Data["Series"][$SerieY]["Axis"];
+
+        $PosArrayX = $this->getPosArray($SerieValuesX, $SerieXAxis);
+        if (!is_array($PosArrayX)) {
+            $Value = $PosArrayX;
+            $PosArrayX = [];
+            $PosArrayX[0] = $Value;
+        }
+        $PosArrayY = $this->getPosArray($SerieValuesY, $SerieYAxis);
+        if (!is_array($PosArrayY)) {
+            $Value = $PosArrayY;
+            $PosArrayY = [];
+            $PosArrayY[0] = $Value;
+        }
+
+        foreach ($Points as $Key => $Point) {
+            if (isset($PosArrayX[$Point]) && isset($PosArrayY[$Point])) {
+                $X = floor($PosArrayX[$Point]);
+                $Y = floor($PosArrayY[$Point]);
+
+                if ($DrawPoint == LABEL_POINT_CIRCLE) {
+                    $this->pChartObject->drawFilledCircle(
+                        $X,
+                        $Y,
+                        3,
+                        [
+                            "R" => 255,
+                            "G" => 255,
+                            "B" => 255,
+                            "BorderR" => 0,
+                            "BorderG" => 0,
+                            "BorderB" => 0
+                        ]
+                    );
+                } elseif ($DrawPoint == LABEL_POINT_BOX) {
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - 2,
+                        $Y - 2,
+                        $X + 2,
+                        $Y + 2,
+                        [
+                            "R" => 255,
+                            "G" => 255,
+                            "B" => 255,
+                            "BorderR" => 0,
+                            "BorderG" => 0,
+                            "BorderB" => 0
+                        ]
+                    );
+                }
+                $Serie = [];
+                $Serie["R"] = $Series["Color"]["R"];
+                $Serie["G"] = $Series["Color"]["G"];
+                $Serie["B"] = $Series["Color"]["B"];
+                $Serie["Alpha"] = $Series["Color"]["Alpha"];
+
+                $XAxisMode = $Data["Axis"][$SerieXAxis]["Display"];
+                $XAxisFormat = $Data["Axis"][$SerieXAxis]["Format"];
+                $XAxisUnit = $Data["Axis"][$SerieXAxis]["Unit"];
+                if ($Decimals == null) {
+                    $XValue = $SerieValuesX[$Point];
+                } else {
+                    $XValue = round($SerieValuesX[$Point], $Decimals);
+                }
+                $XValue = $this->pChartObject->scaleFormat($XValue, $XAxisMode, $XAxisFormat, $XAxisUnit);
+
+                $YAxisMode = $Data["Axis"][$SerieYAxis]["Display"];
+                $YAxisFormat = $Data["Axis"][$SerieYAxis]["Format"];
+                $YAxisUnit = $Data["Axis"][$SerieYAxis]["Unit"];
+                if ($Decimals == null) {
+                    $YValue = $SerieValuesY[$Point];
+                } else {
+                    $YValue = round($SerieValuesY[$Point], $Decimals);
+                }
+                $YValue = $this->pChartObject->scaleFormat($YValue, $YAxisMode, $YAxisFormat, $YAxisUnit);
+
+                $Caption = sprintf("%s / %s", $XValue, $YValue);
+
+                if (isset($Series["Description"])) {
+                    $Description = $Series["Description"];
+                } else {
+                    $Description = "No description";
+                }
+                $Series = [["Format" => $Serie, "Caption" => $Caption]];
+
+                $this->pChartObject->drawLabelBox($X, $Y - 3, $Description, $Series, $Format);
+            }
+        }
+    }
+
+    /**
+     * Draw a Scatter threshold
+     * @param mixed $Value
+     * @param array $Format
+     * @return array
+     */
+    public function drawScatterThreshold($Value, array $Format = [])
+    {
+        $AxisID = isset($Format["AxisID"]) ? $Format["AxisID"] : 0;
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 50;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 3;
+        $Wide = isset($Format["Wide"]) ? $Format["Wide"] : false;
+        $WideFactor = isset($Format["WideFactor"]) ? $Format["WideFactor"] : 5;
+        $WriteCaption = isset($Format["WriteCaption"]) ? $Format["WriteCaption"] : false;
+        $Caption = isset($Format["Caption"]) ? $Format["Caption"] : null;
+        $CaptionAlign = isset($Format["CaptionAlign"]) ? $Format["CaptionAlign"] : CAPTION_LEFT_TOP;
+        $CaptionOffset = isset($Format["CaptionOffset"]) ? $Format["CaptionOffset"] : 10;
+        $CaptionR = isset($Format["CaptionR"]) ? $Format["CaptionR"] : 255;
+        $CaptionG = isset($Format["CaptionG"]) ? $Format["CaptionG"] : 255;
+        $CaptionB = isset($Format["CaptionB"]) ? $Format["CaptionB"] : 255;
+        $CaptionAlpha = isset($Format["CaptionAlpha"]) ? $Format["CaptionAlpha"] : 100;
+        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : true;
+        $DrawBoxBorder = isset($Format["DrawBoxBorder"]) ? $Format["DrawBoxBorder"] : false;
+        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 5;
+        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : true;
+        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 3;
+        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
+        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
+        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 20;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
+        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 255;
+        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 255;
+        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 255;
+        $BoxBorderAlpha = isset($Format["BoxBorderAlpha"]) ? $Format["BoxBorderAlpha"] : 100;
+
+        $CaptionSettings = [
+            "DrawBox" => $DrawBox,
+            "DrawBoxBorder" => $DrawBoxBorder,
+            "BorderOffset" => $BorderOffset,
+            "BoxRounded" => $BoxRounded,
+            "RoundedRadius" => $RoundedRadius,
+            "BoxR" => $BoxR,
+            "BoxG" => $BoxG,
+            "BoxB" => $BoxB,
+            "BoxAlpha" => $BoxAlpha,
+            "BoxSurrounding" => $BoxSurrounding,
+            "BoxBorderR" => $BoxBorderR,
+            "BoxBorderG" => $BoxBorderG,
+            "BoxBorderB" => $BoxBorderB,
+            "BoxBorderAlpha" => $BoxBorderAlpha,
+            "R" => $CaptionR,
+            "G" => $CaptionG,
+            "B" => $CaptionB,
+            "Alpha" => $CaptionAlpha
+        ];
+
+        if ($Caption == null) {
+            $Caption = $Value;
+        }
+
+        $Data = $this->pDataObject->getData();
+
+        if (!isset($Data["Axis"][$AxisID])) {
+            return -1;
+        }
+
+        if ($Data["Axis"][$AxisID]["Identity"] == AXIS_Y) {
+            $X1 = $this->pChartObject->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"];
+            $X2 = $this->pChartObject->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"];
+            $Y = $this->getPosArray($Value, $AxisID);
+
+            $this->pChartObject->drawLine(
+                $X1,
+                $Y,
+                $X2,
+                $Y,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "Ticks" => $Ticks,
+                    "Weight" => $Weight
+                ]
+            );
+
+            if ($Wide) {
+                $this->pChartObject->drawLine(
+                    $X1,
+                    $Y - 1,
+                    $X2,
+                    $Y - 1,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha / $WideFactor,
+                        "Ticks" => $Ticks
+                    ]
+                );
+                $this->pChartObject->drawLine(
+                    $X1,
+                    $Y + 1,
+                    $X2,
+                    $Y + 1,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha / $WideFactor,
+                        "Ticks" => $Ticks
+                    ]
+                );
+            }
+
+            if ($WriteCaption) {
+                if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                    $X = $this->pChartObject->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"] + $CaptionOffset;
+                    $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLELEFT;
+                } else {
+                    $X = $this->pChartObject->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"] - $CaptionOffset;
+                    $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
+                }
+                $this->pChartObject->drawText($X, $Y, $Caption, $CaptionSettings);
+            }
+
+            return ["Y" => $Y];
+        } elseif ($Data["Axis"][$AxisID]["Identity"] == AXIS_X) {
+            $X = $this->getPosArray($Value, $AxisID);
+            $Y1 = $this->pChartObject->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"];
+            $Y2 = $this->pChartObject->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"];
+
+            $this->pChartObject->drawLine(
+                $X,
+                $Y1,
+                $X,
+                $Y2,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "Ticks" => $Ticks,
+                    "Weight" => $Weight
+                ]
+            );
+
+            if ($Wide) {
+                $this->pChartObject->drawLine(
+                    $X - 1,
+                    $Y1,
+                    $X - 1,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha / $WideFactor, "Ticks" => $Ticks]
+                );
+                $this->pChartObject->drawLine(
+                    $X + 1,
+                    $Y1,
+                    $X + 1,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha / $WideFactor, "Ticks" => $Ticks]
+                );
+            }
+
+            if ($WriteCaption) {
+                if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                    $Y = $this->pChartObject->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"] + $CaptionOffset;
+                    $CaptionSettings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                } else {
+                    $Y = $this->pChartObject->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"] - $CaptionOffset;
+                    $CaptionSettings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
+                }
+
+                $CaptionSettings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                $this->pChartObject->drawText($X, $Y, $Caption, $CaptionSettings);
+            }
+
+            return ["X" => $X];
+        }
+    }
+
+    /**
+     * Draw a Scatter threshold area
+     * @param int|float $Value1
+     * @param int|float $Value2
+     * @param array $Format
+     * @return type
+     */
+    public function drawScatterThresholdArea($Value1, $Value2, array $Format = [])
+    {
+        $AxisID = isset($Format["AxisID"]) ? $Format["AxisID"] : 0;
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 20;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : true;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha + 20;
+        $BorderTicks = isset($Format["BorderTicks"]) ? $Format["BorderTicks"] : 2;
+        $AreaName = isset($Format["AreaName"]) ? $Format["AreaName"] : "La ouate de phoque"; //null;
+        $NameAngle = isset($Format["NameAngle"]) ? $Format["NameAngle"] : ZONE_NAME_ANGLE_AUTO;
+        $NameR = isset($Format["NameR"]) ? $Format["NameR"] : 255;
+        $NameG = isset($Format["NameG"]) ? $Format["NameG"] : 255;
+        $NameB = isset($Format["NameB"]) ? $Format["NameB"] : 255;
+        $NameAlpha = isset($Format["NameAlpha"]) ? $Format["NameAlpha"] : 100;
+        $DisableShadowOnArea = isset($Format["DisableShadowOnArea"]) ? $Format["DisableShadowOnArea"] : true;
+
+        if ($Value1 > $Value2) {
+            list($Value1, $Value2) = [$Value2, $Value1];
+        }
+
+        $RestoreShadow = $this->pChartObject->Shadow;
+        if ($DisableShadowOnArea && $this->pChartObject->Shadow) {
+            $this->pChartObject->Shadow = false;
+        }
+
+        if ($BorderAlpha > 100) {
+            $BorderAlpha = 100;
+        }
+
+        $Data = $this->pDataObject->getData();
+
+        if (!isset($Data["Axis"][$AxisID])) {
+            return -1;
+        }
+
+        if ($Data["Axis"][$AxisID]["Identity"] == AXIS_X) {
+            $Y1 = $this->pChartObject->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"];
+            $Y2 = $this->pChartObject->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"];
+            $X1 = $this->getPosArray($Value1, $AxisID);
+            $X2 = $this->getPosArray($Value2, $AxisID);
+
+            if ($X1 <= $this->pChartObject->GraphAreaX1) {
+                $X1 = $this->pChartObject->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($X2 >= $this->pChartObject->GraphAreaX2) {
+                $X2 = $this->pChartObject->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+
+            $this->pChartObject->drawFilledRectangle(
+                $X1,
+                $Y1,
+                $X2,
+                $Y2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+
+            if ($Border) {
+                $this->pChartObject->drawLine(
+                    $X1,
+                    $Y1,
+                    $X1,
+                    $Y2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->pChartObject->drawLine(
+                    $X2,
+                    $Y1,
+                    $X2,
+                    $Y2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($X2 - $X1) / 2 + $X1;
+                $YPos = ($Y2 - $Y1) / 2 + $Y1;
+
+                if ($NameAngle == ZONE_NAME_ANGLE_AUTO) {
+                    $TxtPos = $this->pChartObject->getTextBox(
+                        $XPos,
+                        $YPos,
+                        $this->pChartObject->FontName,
+                        $this->pChartObject->FontSize,
+                        0,
+                        $AreaName
+                    );
+                    $TxtWidth = $TxtPos[1]["X"] - $TxtPos[0]["X"];
+                    if (abs($X2 - $X1) > $TxtWidth) {
+                        $NameAngle = 0;
+                    } else {
+                        $NameAngle = 90;
+                    }
+                }
+                $this->pChartObject->Shadow = $RestoreShadow;
+                $this->pChartObject->drawText(
+                    $XPos,
+                    $YPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => $NameAngle,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $this->pChartObject->Shadow = false;
+                }
+            }
+
+            $this->pChartObject->Shadow = $RestoreShadow;
+            return ["X1" => $X1, "X2" => $X2];
+        } elseif ($Data["Axis"][$AxisID]["Identity"] == AXIS_Y) {
+            $X1 = $this->pChartObject->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"];
+            $X2 = $this->pChartObject->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"];
+            $Y1 = $this->getPosArray($Value1, $AxisID);
+            $Y2 = $this->getPosArray($Value2, $AxisID);
+
+            if ($Y1 >= $this->pChartObject->GraphAreaY2) {
+                $Y1 = $this->pChartObject->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($Y2 <= $this->pChartObject->GraphAreaY1) {
+                $Y2 = $this->pChartObject->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+
+            $this->pChartObject->drawFilledRectangle(
+                $X1,
+                $Y1,
+                $X2,
+                $Y2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+
+            if ($Border) {
+                $this->pChartObject->drawLine(
+                    $X1,
+                    $Y1,
+                    $X2,
+                    $Y1,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->pChartObject->drawLine(
+                    $X1,
+                    $Y2,
+                    $X2,
+                    $Y2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($X2 - $X1) / 2 + $X1;
+                $YPos = ($Y2 - $Y1) / 2 + $Y1;
+
+                $this->pChartObject->Shadow = $RestoreShadow;
+                $this->pChartObject->drawText(
+                    $YPos,
+                    $XPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => 0,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $$this->pChartObject->Shadow = false;
+                }
+            }
+
+            $this->pChartObject->Shadow = $RestoreShadow;
+            return ["Y1" => $Y1, "Y2" => $Y2];
+        }
+    }
+}

+ 180 - 0
vendor/szymach/c-pchart/src/Chart/Split.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+
+/**
+ *    Split - class to draw spline splitted charts
+ *
+ *    Version     : 2.1.4
+ *    Made by     : Jean-Damien POGOLOTTI
+ *    Last Update : 19/01/2014
+ *
+ *    This file can be distributed under the license you can find at :
+ *
+ *                      http://www.pchart.net/license
+ *
+ *    You can find the whole class documentation on the pChart web site.
+ */
+class Split
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * Create the encoded string
+     * @param Image $Object
+     * @param Data $Values
+     * @param array $Format
+     */
+    public function drawSplitPath(Image $Object, Data $Values, array $Format = [])
+    {
+        $this->pChartObject = $Object;
+
+        $Spacing = isset($Format["Spacing"]) ? $Format["Spacing"] : 20;
+        $TextPadding = isset($Format["TextPadding"]) ? $Format["TextPadding"] : 2;
+        $TextPos = isset($Format["TextPos"]) ? $Format["TextPos"] : TEXT_POS_TOP;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Force = isset($Format["Force"]) ? $Format["Force"] : 70;
+        $Segments = isset($Format["Segments"]) ? $Format["Segments"] : 15;
+        $X1 = $Object->GraphAreaX1;
+        $Y1 = $Object->GraphAreaY1;
+        $X2 = $Object->GraphAreaX2;
+        $Y2 = $Object->GraphAreaY2;
+
+        /* Data Processing */
+        $Data = $Values->getData();
+        $Palette = $Values->getPalette();
+
+        $LabelSerie = $Data["Abscissa"];
+        $DataSerie = [];
+
+        foreach ($Data["Series"] as $SerieName => $Value) {
+            if ($SerieName != $LabelSerie && empty($DataSerie)) {
+                $DataSerie = $SerieName;
+            }
+        }
+
+        $DataSerieSum = array_sum($Data["Series"][$DataSerie]["Data"]);
+        $DataSerieCount = count($Data["Series"][$DataSerie]["Data"]);
+
+        /* Scale Processing */
+        if ($TextPos == TEXT_POS_RIGHT) {
+            $YScale = (($Y2 - $Y1) - (($DataSerieCount + 1) * $Spacing)) / $DataSerieSum;
+        } else {
+            $YScale = (($Y2 - $Y1) - ($DataSerieCount * $Spacing)) / $DataSerieSum;
+        }
+        $LeftHeight = $DataSerieSum * $YScale;
+
+        /* Re-compute graph width depending of the text mode choosen */
+        if ($TextPos == TEXT_POS_RIGHT) {
+            $MaxWidth = 0;
+            foreach ($Data["Series"][$LabelSerie]["Data"] as $Key => $Label) {
+                $Boundardies = $Object->getTextBox(0, 0, $Object->FontName, $Object->FontSize, 0, $Label);
+                if ($Boundardies[1]["X"] > $MaxWidth) {
+                    $MaxWidth = $Boundardies[1]["X"] + $TextPadding * 2;
+                }
+            }
+            $X2 = $X2 - $MaxWidth;
+        }
+
+        /* Drawing */
+        $LeftY = ((($Y2 - $Y1) / 2) + $Y1) - ($LeftHeight / 2);
+        $RightY = $Y1;
+
+        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
+            if (isset($Data["Series"][$LabelSerie]["Data"][$Key])) {
+                $Label = $Data["Series"][$LabelSerie]["Data"][$Key];
+            } else {
+                $Label = "-";
+            }
+            $LeftY1 = $LeftY;
+            $LeftY2 = $LeftY + $Value * $YScale;
+
+            $RightY1 = $RightY + $Spacing;
+            $RightY2 = $RightY + $Spacing + $Value * $YScale;
+
+            $Settings = [
+                "R" => $Palette[$Key]["R"],
+                "G" => $Palette[$Key]["G"],
+                "B" => $Palette[$Key]["B"],
+                "Alpha" => $Palette[$Key]["Alpha"],
+                "NoDraw" => true,
+                "Segments" => $Segments,
+                "Surrounding" => $Surrounding
+            ];
+
+
+            $Angle = $Object->getAngle($X2, $RightY1, $X1, $LeftY1);
+            $VectorX1 = cos(deg2rad($Angle + 90)) * $Force + ($X2 - $X1) / 2 + $X1;
+            $VectorY1 = sin(deg2rad($Angle + 90)) * $Force + ($RightY1 - $LeftY1) / 2 + $LeftY1;
+            $VectorX2 = cos(deg2rad($Angle - 90)) * $Force + ($X2 - $X1) / 2 + $X1;
+            $VectorY2 = sin(deg2rad($Angle - 90)) * $Force + ($RightY1 - $LeftY1) / 2 + $LeftY1;
+
+            $Points = $Object->drawBezier(
+                $X1,
+                $LeftY1,
+                $X2,
+                $RightY1,
+                $VectorX1,
+                $VectorY1,
+                $VectorX2,
+                $VectorY2,
+                $Settings
+            );
+
+            $PolyGon = [];
+            foreach ($Points as $Key => $Pos) {
+                $PolyGon[] = $Pos["X"];
+                $PolyGon[] = $Pos["Y"];
+            }
+
+            $Angle = $Object->getAngle($X2, $RightY2, $X1, $LeftY2);
+            $VectorX1 = cos(deg2rad($Angle + 90)) * $Force + ($X2 - $X1) / 2 + $X1;
+            $VectorY1 = sin(deg2rad($Angle + 90)) * $Force + ($RightY2 - $LeftY2) / 2 + $LeftY2;
+            $VectorX2 = cos(deg2rad($Angle - 90)) * $Force + ($X2 - $X1) / 2 + $X1;
+            $VectorY2 = sin(deg2rad($Angle - 90)) * $Force + ($RightY2 - $LeftY2) / 2 + $LeftY2;
+
+            $Points = $Object->drawBezier(
+                $X1,
+                $LeftY2,
+                $X2,
+                $RightY2,
+                $VectorX1,
+                $VectorY1,
+                $VectorX2,
+                $VectorY2,
+                $Settings
+            );
+            $Points = array_reverse($Points);
+            foreach ($Points as $Key => $Pos) {
+                $PolyGon[] = $Pos["X"];
+                $PolyGon[] = $Pos["Y"];
+            }
+
+            $Object->drawPolygon($PolyGon, $Settings);
+
+            if ($TextPos == TEXT_POS_RIGHT) {
+                $Object->drawText(
+                    $X2 + $TextPadding,
+                    ($RightY2 - $RightY1) / 2 + $RightY1,
+                    $Label,
+                    ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                );
+            } else {
+                $Object->drawText(
+                    $X2,
+                    $RightY1 - $TextPadding,
+                    $Label,
+                    ["Align" => TEXT_ALIGN_BOTTOMRIGHT]
+                );
+            }
+            $LeftY = $LeftY2;
+            $RightY = $RightY2;
+        }
+    }
+}

+ 1190 - 0
vendor/szymach/c-pchart/src/Chart/Spring.php

@@ -0,0 +1,1190 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Image;
+
+/**
+ *  Spring - class to draw spring graphs
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Spring
+{
+    /**
+     * @var array
+     */
+    public $History = [];
+
+    /**
+     * @var array
+     */
+    public $Data = [];
+
+    /**
+     * @var array
+     */
+    public $Default = [];
+
+    /**
+     * @var array
+     */
+    public $Labels = [];
+
+    /**
+     * @var array
+     */
+    public $Links = [];
+
+    /**
+     * @var type
+     */
+    public $pChartObject;
+
+    /**
+     * @var int
+     */
+    public $X1;
+
+    /**
+     * @var int
+     */
+    public $Y1;
+
+    /**
+     * @var int
+     */
+    public $X2;
+
+    /**
+     * @var int
+     */
+    public $Y2;
+
+    /**
+     * @var boolean
+     */
+    public $AutoComputeFreeZone = false;
+
+    /**
+     * @var int|float
+     */
+    private $MagneticForceA;
+
+    /**
+     * @var int|float
+     */
+    private $MagneticForceR;
+
+    /**
+     * @var int|float
+     */
+    private $RingSize;
+
+    public function __construct()
+    {
+        /* Set nodes defaults */
+        $this->Default["R"] = 255;
+        $this->Default["G"] = 255;
+        $this->Default["B"] = 255;
+        $this->Default["Alpha"] = 100;
+        $this->Default["BorderR"] = 0;
+        $this->Default["BorderG"] = 0;
+        $this->Default["BorderB"] = 0;
+        $this->Default["BorderAlpha"] = 100;
+        $this->Default["Surrounding"] = null;
+        $this->Default["BackgroundR"] = 255;
+        $this->Default["BackgroundG"] = 255;
+        $this->Default["BackgroundB"] = 255;
+        $this->Default["BackgroundAlpha"] = 0;
+        $this->Default["Force"] = 1;
+        $this->Default["NodeType"] = NODE_TYPE_FREE;
+        $this->Default["Size"] = 5;
+        $this->Default["Shape"] = NODE_SHAPE_CIRCLE;
+        $this->Default["FreeZone"] = 40;
+        $this->Default["LinkR"] = 0;
+        $this->Default["LinkG"] = 0;
+        $this->Default["LinkB"] = 0;
+        $this->Default["LinkAlpha"] = 0;
+
+        $this->Labels["Type"] = LABEL_CLASSIC;
+        $this->Labels["R"] = 0;
+        $this->Labels["G"] = 0;
+        $this->Labels["B"] = 0;
+        $this->Labels["Alpha"] = 100;
+    }
+
+    /**
+     * Set default links options
+     * @param array $Settings
+     */
+    public function setLinkDefaults(array $Settings = [])
+    {
+        if (isset($Settings["R"])) {
+            $this->Default["LinkR"] = $Settings["R"];
+        }
+        if (isset($Settings["G"])) {
+            $this->Default["LinkG"] = $Settings["G"];
+        }
+        if (isset($Settings["B"])) {
+            $this->Default["LinkB"] = $Settings["B"];
+        }
+        if (isset($Settings["Alpha"])) {
+            $this->Default["LinkAlpha"] = $Settings["Alpha"];
+        }
+    }
+
+    /**
+     * Set default links options
+     * @param array $Settings
+     */
+    public function setLabelsSettings(array $Settings = [])
+    {
+        if (isset($Settings["Type"])) {
+            $this->Labels["Type"] = $Settings["Type"];
+        }
+        if (isset($Settings["R"])) {
+            $this->Labels["R"] = $Settings["R"];
+        }
+        if (isset($Settings["G"])) {
+            $this->Labels["G"] = $Settings["G"];
+        }
+        if (isset($Settings["B"])) {
+            $this->Labels["B"] = $Settings["B"];
+        }
+        if (isset($Settings["Alpha"])) {
+            $this->Labels["Alpha"] = $Settings["Alpha"];
+        }
+    }
+
+    /**
+     * Auto compute the FreeZone size based on the number of connections
+     */
+    public function autoFreeZone()
+    {
+        /* Check connections reciprocity */
+        foreach ($this->Data as $Key => $Settings) {
+            if (isset($Settings["Connections"])) {
+                $this->Data[$Key]["FreeZone"] = count($Settings["Connections"]) * 10 + 20;
+            } else {
+                $this->Data[$Key]["FreeZone"] = 20;
+            }
+        }
+    }
+
+    /**
+     * Set link properties
+     * @param int $FromNode
+     * @param int $ToNode
+     * @param array $Settings
+     * @return null|int
+     */
+    public function linkProperties($FromNode, $ToNode, array $Settings)
+    {
+        if (!isset($this->Data[$FromNode])) {
+            return 0;
+        }
+        if (!isset($this->Data[$ToNode])) {
+            return 0;
+        }
+
+        $R = isset($Settings["R"]) ? $Settings["R"] : 0;
+        $G = isset($Settings["G"]) ? $Settings["G"] : 0;
+        $B = isset($Settings["B"]) ? $Settings["B"] : 0;
+        $Alpha = isset($Settings["Alpha"]) ? $Settings["Alpha"] : 100;
+        $Name = isset($Settings["Name"]) ? $Settings["Name"] : null;
+        $Ticks = isset($Settings["Ticks"]) ? $Settings["Ticks"] : null;
+
+        $this->Links[$FromNode][$ToNode]["R"] = $R;
+        $this->Links[$ToNode][$FromNode]["R"] = $R;
+        $this->Links[$FromNode][$ToNode]["G"] = $G;
+        $this->Links[$ToNode][$FromNode]["G"] = $G;
+        $this->Links[$FromNode][$ToNode]["B"] = $B;
+        $this->Links[$ToNode][$FromNode]["B"] = $B;
+        $this->Links[$FromNode][$ToNode]["Alpha"] = $Alpha;
+        $this->Links[$ToNode][$FromNode]["Alpha"] = $Alpha;
+        $this->Links[$FromNode][$ToNode]["Name"] = $Name;
+        $this->Links[$ToNode][$FromNode]["Name"] = $Name;
+        $this->Links[$FromNode][$ToNode]["Ticks"] = $Ticks;
+        $this->Links[$ToNode][$FromNode]["Ticks"] = $Ticks;
+    }
+
+    /**
+     * @param array $Settings
+     */
+    public function setNodeDefaults(array $Settings = [])
+    {
+        if (isset($Settings["R"])) {
+            $this->Default["R"] = $Settings["R"];
+        }
+        if (isset($Settings["G"])) {
+            $this->Default["G"] = $Settings["G"];
+        }
+        if (isset($Settings["B"])) {
+            $this->Default["B"] = $Settings["B"];
+        }
+        if (isset($Settings["Alpha"])) {
+            $this->Default["Alpha"] = $Settings["Alpha"];
+        }
+        if (isset($Settings["BorderR"])) {
+            $this->Default["BorderR"] = $Settings["BorderR"];
+        }
+        if (isset($Settings["BorderG"])) {
+            $this->Default["BorderG"] = $Settings["BorderG"];
+        }
+        if (isset($Settings["BorderB"])) {
+            $this->Default["BorderB"] = $Settings["BorderB"];
+        }
+        if (isset($Settings["BorderAlpha"])) {
+            $this->Default["BorderAlpha"] = $Settings["BorderAlpha"];
+        }
+        if (isset($Settings["Surrounding"])) {
+            $this->Default["Surrounding"] = $Settings["Surrounding"];
+        }
+        if (isset($Settings["BackgroundR"])) {
+            $this->Default["BackgroundR"] = $Settings["BackgroundR"];
+        }
+        if (isset($Settings["BackgroundG"])) {
+            $this->Default["BackgroundG"] = $Settings["BackgroundG"];
+        }
+        if (isset($Settings["BackgroundB"])) {
+            $this->Default["BackgroundB"] = $Settings["BackgroundB"];
+        }
+        if (isset($Settings["BackgroundAlpha"])) {
+            $this->Default["BackgroundAlpha"] = $Settings["BackgroundAlpha"];
+        }
+        if (isset($Settings["NodeType"])) {
+            $this->Default["NodeType"] = $Settings["NodeType"];
+        }
+        if (isset($Settings["Size"])) {
+            $this->Default["Size"] = $Settings["Size"];
+        }
+        if (isset($Settings["Shape"])) {
+            $this->Default["Shape"] = $Settings["Shape"];
+        }
+        if (isset($Settings["FreeZone"])) {
+            $this->Default["FreeZone"] = $Settings["FreeZone"];
+        }
+    }
+
+    /**
+     * Add a node
+     * @param int $NodeID
+     * @param array $Settings
+     * @return null|int
+     */
+    public function addNode($NodeID, array $Settings = [])
+    {
+        /* if the node already exists, ignore */
+        if (isset($this->Data[$NodeID])) {
+            return 0;
+        }
+
+        $Name = isset($Settings["Name"]) ? $Settings["Name"] : "Node " . $NodeID;
+        $Connections = isset($Settings["Connections"]) ? $Settings["Connections"] : null;
+
+        $R = isset($Settings["R"]) ? $Settings["R"] : $this->Default["R"];
+        $G = isset($Settings["G"]) ? $Settings["G"] : $this->Default["G"];
+        $B = isset($Settings["B"]) ? $Settings["B"] : $this->Default["B"];
+        $Alpha = isset($Settings["Alpha"]) ? $Settings["Alpha"] : $this->Default["Alpha"];
+        $BorderR = isset($Settings["BorderR"]) ? $Settings["BorderR"] : $this->Default["BorderR"];
+        $BorderG = isset($Settings["BorderG"]) ? $Settings["BorderG"] : $this->Default["BorderG"];
+        $BorderB = isset($Settings["BorderB"]) ? $Settings["BorderB"] : $this->Default["BorderB"];
+        $BorderAlpha = isset($Settings["BorderAlpha"]) ? $Settings["BorderAlpha"] : $this->Default["BorderAlpha"];
+        $Surrounding = isset($Settings["Surrounding"]) ? $Settings["Surrounding"] : $this->Default["Surrounding"];
+        $BackgroundR = isset($Settings["BackgroundR"]) ? $Settings["BackgroundR"] : $this->Default["BackgroundR"];
+        $BackgroundG = isset($Settings["BackgroundG"]) ? $Settings["BackgroundG"] : $this->Default["BackgroundG"];
+        $BackgroundB = isset($Settings["BackgroundB"]) ? $Settings["BackgroundB"] : $this->Default["BackgroundB"];
+        $BackgroundAlpha = isset($Settings["BackgroundAlpha"])
+            ? $Settings["BackgroundAlpha"] : $this->Default["BackgroundAlpha"]
+        ;
+        $Force = isset($Settings["Force"]) ? $Settings["Force"] : $this->Default["Force"];
+        $NodeType = isset($Settings["NodeType"]) ? $Settings["NodeType"] : $this->Default["NodeType"];
+        $Size = isset($Settings["Size"]) ? $Settings["Size"] : $this->Default["Size"];
+        $Shape = isset($Settings["Shape"]) ? $Settings["Shape"] : $this->Default["Shape"];
+        $FreeZone = isset($Settings["FreeZone"]) ? $Settings["FreeZone"] : $this->Default["FreeZone"];
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        $this->Data[$NodeID]["R"] = $R;
+        $this->Data[$NodeID]["G"] = $G;
+        $this->Data[$NodeID]["B"] = $B;
+        $this->Data[$NodeID]["Alpha"] = $Alpha;
+        $this->Data[$NodeID]["BorderR"] = $BorderR;
+        $this->Data[$NodeID]["BorderG"] = $BorderG;
+        $this->Data[$NodeID]["BorderB"] = $BorderB;
+        $this->Data[$NodeID]["BorderAlpha"] = $BorderAlpha;
+        $this->Data[$NodeID]["BackgroundR"] = $BackgroundR;
+        $this->Data[$NodeID]["BackgroundG"] = $BackgroundG;
+        $this->Data[$NodeID]["BackgroundB"] = $BackgroundB;
+        $this->Data[$NodeID]["BackgroundAlpha"] = $BackgroundAlpha;
+        $this->Data[$NodeID]["Name"] = $Name;
+        $this->Data[$NodeID]["Force"] = $Force;
+        $this->Data[$NodeID]["Type"] = $NodeType;
+        $this->Data[$NodeID]["Size"] = $Size;
+        $this->Data[$NodeID]["Shape"] = $Shape;
+        $this->Data[$NodeID]["FreeZone"] = $FreeZone;
+        if ($Connections != null) {
+            if (is_array($Connections)) {
+                foreach ($Connections as $Key => $Value) {
+                    $this->Data[$NodeID]["Connections"][] = $Value;
+                }
+            } else {
+                $this->Data[$NodeID]["Connections"][] = $Connections;
+            }
+        }
+    }
+
+    /**
+     * Set color attribute for a list of nodes
+     * @param array|string $Nodes
+     * @param array $Settings
+     */
+    public function setNodesColor($Nodes, array $Settings = [])
+    {
+        if (is_array($Nodes)) {
+            foreach ($Nodes as $Key => $NodeID) {
+                if (isset($this->Data[$NodeID])) {
+                    if (isset($Settings["R"])) {
+                        $this->Data[$NodeID]["R"] = $Settings["R"];
+                    }
+                    if (isset($Settings["G"])) {
+                        $this->Data[$NodeID]["G"] = $Settings["G"];
+                    }
+                    if (isset($Settings["B"])) {
+                        $this->Data[$NodeID]["B"] = $Settings["B"];
+                    }
+                    if (isset($Settings["Alpha"])) {
+                        $this->Data[$NodeID]["Alpha"] = $Settings["Alpha"];
+                    }
+                    if (isset($Settings["BorderR"])) {
+                        $this->Data[$NodeID]["BorderR"] = $Settings["BorderR"];
+                    }
+                    if (isset($Settings["BorderG"])) {
+                        $this->Data[$NodeID]["BorderG"] = $Settings["BorderG"];
+                    }
+                    if (isset($Settings["BorderB"])) {
+                        $this->Data[$NodeID]["BorderB"] = $Settings["BorderB"];
+                    }
+                    if (isset($Settings["BorderAlpha"])) {
+                        $this->Data[$NodeID]["BorderAlpha"] = $Settings["BorderAlpha"];
+                    }
+                    if (isset($Settings["Surrounding"])) {
+                        $this->Data[$NodeID]["BorderR"] = $this->Data[$NodeID]["R"] + $Settings["Surrounding"];
+                        $this->Data[$NodeID]["BorderG"] = $this->Data[$NodeID]["G"] + $Settings["Surrounding"];
+                        $this->Data[$NodeID]["BorderB"] = $this->Data[$NodeID]["B"] + $Settings["Surrounding"];
+                    }
+                }
+            }
+        } else {
+            if (isset($Settings["R"])) {
+                $this->Data[$Nodes]["R"] = $Settings["R"];
+            }
+            if (isset($Settings["G"])) {
+                $this->Data[$Nodes]["G"] = $Settings["G"];
+            }
+            if (isset($Settings["B"])) {
+                $this->Data[$Nodes]["B"] = $Settings["B"];
+            }
+            if (isset($Settings["Alpha"])) {
+                $this->Data[$Nodes]["Alpha"] = $Settings["Alpha"];
+            }
+            if (isset($Settings["BorderR"])) {
+                $this->Data[$Nodes]["BorderR"] = $Settings["BorderR"];
+            }
+            if (isset($Settings["BorderG"])) {
+                $this->Data[$Nodes]["BorderG"] = $Settings["BorderG"];
+            }
+            if (isset($Settings["BorderB"])) {
+                $this->Data[$Nodes]["BorderB"] = $Settings["BorderB"];
+            }
+            if (isset($Settings["BorderAlpha"])) {
+                $this->Data[$Nodes]["BorderAlpha"] = $Settings["BorderAlpha"];
+            }
+            if (isset($Settings["Surrounding"])) {
+                $this->Data[$Nodes]["BorderR"] = $this->Data[$NodeID]["R"] + $Settings["Surrounding"];
+                $this->Data[$NodeID]["BorderG"] = $this->Data[$NodeID]["G"] + $Settings["Surrounding"];
+                $this->Data[$NodeID]["BorderB"] = $this->Data[$NodeID]["B"] + $Settings["Surrounding"];
+            }
+        }
+    }
+
+    /**
+     * Returns all the nodes details
+     * @return array
+     */
+    public function dumpNodes()
+    {
+        return $this->Data;
+    }
+
+    /**
+     * Check if a connection exists and create it if required
+     * @param string|int $SourceID
+     * @param string|int $TargetID
+     * @return boolean|null
+     */
+    public function checkConnection($SourceID, $TargetID)
+    {
+        if (isset($this->Data[$SourceID]["Connections"])) {
+            foreach ($this->Data[$SourceID]["Connections"] as $ConnectionID) {
+                if ($TargetID == $ConnectionID) {
+                    return true;
+                }
+            }
+        }
+        $this->Data[$SourceID]["Connections"][] = $TargetID;
+    }
+
+    /**
+     * Get the median linked nodes position
+     * @param string $Key
+     * @param int $X
+     * @param int $Y
+     * @return array
+     */
+    public function getMedianOffset($Key, $X, $Y)
+    {
+        $Cpt = 1;
+        if (isset($this->Data[$Key]["Connections"])) {
+            foreach ($this->Data[$Key]["Connections"] as $NodeID) {
+                if (isset($this->Data[$NodeID]["X"])
+                    && isset($this->Data[$NodeID]["Y"])
+                ) {
+                    $X = $X + $this->Data[$NodeID]["X"];
+                    $Y = $Y + $this->Data[$NodeID]["Y"];
+                    $Cpt++;
+                }
+            }
+        }
+        return ["X" => $X / $Cpt, "Y" => $Y / $Cpt];
+    }
+
+    /**
+     * Return the ID of the attached partner with the biggest weight
+     * @param string $Key
+     * @return string
+     */
+    public function getBiggestPartner($Key)
+    {
+        if (!isset($this->Data[$Key]["Connections"])) {
+            return "";
+        }
+
+        $MaxWeight = 0;
+        $Result = "";
+        foreach ($this->Data[$Key]["Connections"] as $Key => $PeerID) {
+            if ($this->Data[$PeerID]["Weight"] > $MaxWeight) {
+                $MaxWeight = $this->Data[$PeerID]["Weight"];
+                $Result = $PeerID;
+            }
+        }
+        return $Result;
+    }
+
+    /**
+     * Do the initial node positions computing pass
+     * @param int $Algorithm
+     */
+    public function firstPass($Algorithm)
+    {
+        $CenterX = ($this->X2 - $this->X1) / 2 + $this->X1;
+        $CenterY = ($this->Y2 - $this->Y1) / 2 + $this->Y1;
+
+        /* Check connections reciprocity */
+        foreach ($this->Data as $Key => $Settings) {
+            if (isset($Settings["Connections"])) {
+                foreach ($Settings["Connections"] as $ID => $ConnectionID) {
+                    $this->checkConnection($ConnectionID, $Key);
+                }
+            }
+        }
+
+        if ($this->AutoComputeFreeZone) {
+            $this->autoFreeZone();
+        }
+
+        /* Get the max number of connections */
+        $MaxConnections = 0;
+        foreach ($this->Data as $Key => $Settings) {
+            if (isset($Settings["Connections"])) {
+                if ($MaxConnections < count($Settings["Connections"])) {
+                    $MaxConnections = count($Settings["Connections"]);
+                }
+            }
+        }
+
+        if ($Algorithm == ALGORITHM_WEIGHTED) {
+            foreach ($this->Data as $Key => $Settings) {
+                if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
+                    $this->Data[$Key]["X"] = $CenterX;
+                    $this->Data[$Key]["Y"] = $CenterY;
+                }
+                if ($Settings["Type"] == NODE_TYPE_FREE) {
+                    if (isset($Settings["Connections"])) {
+                        $Connections = count($Settings["Connections"]);
+                    } else {
+                        $Connections = 0;
+                    }
+
+                    $Ring = $MaxConnections - $Connections;
+                    $Angle = rand(0, 360);
+
+                    $this->Data[$Key]["X"] = cos(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterX;
+                    $this->Data[$Key]["Y"] = sin(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterY;
+                }
+            }
+        } elseif ($Algorithm == ALGORITHM_CENTRAL) {
+            /* Put a weight on each nodes */
+            foreach ($this->Data as $Key => $Settings) {
+                if (isset($Settings["Connections"])) {
+                    $this->Data[$Key]["Weight"] = count($Settings["Connections"]);
+                } else {
+                    $this->Data[$Key]["Weight"] = 0;
+                }
+            }
+
+            $MaxConnections = $MaxConnections + 1;
+            for ($i = $MaxConnections; $i >= 0; $i--) {
+                foreach ($this->Data as $Key => $Settings) {
+                    if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
+                        $this->Data[$Key]["X"] = $CenterX;
+                        $this->Data[$Key]["Y"] = $CenterY;
+                    }
+                    if ($Settings["Type"] == NODE_TYPE_FREE) {
+                        if (isset($Settings["Connections"])) {
+                            $Connections = count($Settings["Connections"]);
+                        } else {
+                            $Connections = 0;
+                        }
+
+                        if ($Connections == $i) {
+                            $BiggestPartner = $this->getBiggestPartner($Key);
+                            if ($BiggestPartner != "") {
+                                $Ring = $this->Data[$BiggestPartner]["FreeZone"];
+                                $Weight = $this->Data[$BiggestPartner]["Weight"];
+                                $AngleDivision = 360 / $this->Data[$BiggestPartner]["Weight"];
+                                $Done = false;
+                                $Tries = 0;
+                                while (!$Done && $Tries <= $Weight * 2) {
+                                    $Tries++;
+                                    $Angle = floor(rand(0, $Weight) * $AngleDivision);
+                                    if (!isset($this->Data[$BiggestPartner]["Angular"][$Angle])
+                                        || !isset($this->Data[$BiggestPartner]["Angular"])
+                                    ) {
+                                        $this->Data[$BiggestPartner]["Angular"][$Angle] = $Angle;
+                                        $Done = true;
+                                    }
+                                }
+                                if (!$Done) {
+                                    $Angle = rand(0, 360);
+                                    $this->Data[$BiggestPartner]["Angular"][$Angle] = $Angle;
+                                }
+
+                                $X = cos(deg2rad($Angle)) * ($Ring) + $this->Data[$BiggestPartner]["X"];
+                                $Y = sin(deg2rad($Angle)) * ($Ring) + $this->Data[$BiggestPartner]["Y"];
+
+                                $this->Data[$Key]["X"] = $X;
+                                $this->Data[$Key]["Y"] = $Y;
+                            }
+                        }
+                    }
+                }
+            }
+        } elseif ($Algorithm == ALGORITHM_CIRCULAR) {
+            $MaxConnections = $MaxConnections + 1;
+            for ($i = $MaxConnections; $i >= 0; $i--) {
+                foreach ($this->Data as $Key => $Settings) {
+                    if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
+                        $this->Data[$Key]["X"] = $CenterX;
+                        $this->Data[$Key]["Y"] = $CenterY;
+                    }
+                    if ($Settings["Type"] == NODE_TYPE_FREE) {
+                        if (isset($Settings["Connections"])) {
+                            $Connections = count($Settings["Connections"]);
+                        } else {
+                            $Connections = 0;
+                        }
+
+                        if ($Connections == $i) {
+                            $Ring = $MaxConnections - $Connections;
+                            $Angle = rand(0, 360);
+
+                            $X = cos(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterX;
+                            $Y = sin(deg2rad($Angle)) * ($Ring * $this->RingSize) + $CenterY;
+
+                            $MedianOffset = $this->getMedianOffset($Key, $X, $Y);
+
+                            $this->Data[$Key]["X"] = $MedianOffset["X"];
+                            $this->Data[$Key]["Y"] = $MedianOffset["Y"];
+                        }
+                    }
+                }
+            }
+        } elseif ($Algorithm == ALGORITHM_RANDOM) {
+            foreach ($this->Data as $Key => $Settings) {
+                if ($Settings["Type"] == NODE_TYPE_FREE) {
+                    $this->Data[$Key]["X"] = $CenterX + rand(-20, 20);
+                    $this->Data[$Key]["Y"] = $CenterY + rand(-20, 20);
+                }
+                if ($Settings["Type"] == NODE_TYPE_CENTRAL) {
+                    $this->Data[$Key]["X"] = $CenterX;
+                    $this->Data[$Key]["Y"] = $CenterY;
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute one pass
+     */
+    public function doPass()
+    {
+        /* Compute vectors */
+        foreach ($this->Data as $Key => $Settings) {
+            if ($Settings["Type"] != NODE_TYPE_CENTRAL) {
+                unset($this->Data[$Key]["Vectors"]);
+
+                $X1 = $Settings["X"];
+                $Y1 = $Settings["Y"];
+
+                /* Repulsion vectors */
+                foreach ($this->Data as $Key2 => $Settings2) {
+                    if ($Key != $Key2) {
+                        $X2 = $this->Data[$Key2]["X"];
+                        $Y2 = $this->Data[$Key2]["Y"];
+                        $FreeZone = $this->Data[$Key2]["FreeZone"];
+
+                        $Distance = $this->getDistance($X1, $Y1, $X2, $Y2);
+                        $Angle = $this->getAngle($X1, $Y1, $X2, $Y2) + 180;
+
+                        /* Nodes too close, repulsion occurs */
+                        if ($Distance < $FreeZone) {
+                            $Force = log(pow(2, $FreeZone - $Distance));
+                            if ($Force > 1) {
+                                $this->Data[$Key]["Vectors"][] = [
+                                    "Type"  => "R",
+                                    "Angle" => ((int) $Angle) % 360,
+                                    "Force" => $Force
+                                ];
+                            }
+                        }
+                    }
+                }
+
+                /* Attraction vectors */
+                if (isset($Settings["Connections"])) {
+                    foreach ($Settings["Connections"] as $ID => $NodeID) {
+                        if (isset($this->Data[$NodeID])) {
+                            $X2 = $this->Data[$NodeID]["X"];
+                            $Y2 = $this->Data[$NodeID]["Y"];
+                            $FreeZone = $this->Data[$Key2]["FreeZone"];
+
+                            $Distance = $this->getDistance($X1, $Y1, $X2, $Y2);
+                            $Angle = $this->getAngle($X1, $Y1, $X2, $Y2);
+
+                            if ($Distance > $FreeZone) {
+                                $Force = log(($Distance - $FreeZone) + 1);
+                            } else {
+                                $Force = log(($FreeZone - $Distance) + 1);
+                                ($Angle = $Angle + 180);
+                            }
+
+                            if ($Force > 1) {
+                                $this->Data[$Key]["Vectors"][] = [
+                                    "Type"  => "A",
+                                    "Angle" => ((int) $Angle) % 360,
+                                    "Force" => $Force
+                                ];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /* Move the nodes accoding to the vectors */
+        foreach ($this->Data as $Key => $Settings) {
+            $X = $Settings["X"];
+            $Y = $Settings["Y"];
+
+            if (isset($Settings["Vectors"]) && $Settings["Type"] != NODE_TYPE_CENTRAL) {
+                foreach ($Settings["Vectors"] as $ID => $Vector) {
+                    $Type = $Vector["Type"];
+                    $Force = $Vector["Force"];
+                    $Angle = $Vector["Angle"];
+                    $Factor = $Type == "A" ? $this->MagneticForceA : $this->MagneticForceR;
+
+                    $X = cos(deg2rad($Angle)) * $Force * $Factor + $X;
+                    $Y = sin(deg2rad($Angle)) * $Force * $Factor + $Y;
+                }
+            }
+
+            $this->Data[$Key]["X"] = $X;
+            $this->Data[$Key]["Y"] = $Y;
+        }
+    }
+
+    /**
+     * @return int|float
+     */
+    public function lastPass()
+    {
+        /* Put everything inside the graph area */
+        foreach ($this->Data as $Key => $Settings) {
+            $X = $Settings["X"];
+            $Y = $Settings["Y"];
+
+            if ($X < $this->X1) {
+                $X = $this->X1;
+            }
+            if ($X > $this->X2) {
+                $X = $this->X2;
+            }
+            if ($Y < $this->Y1) {
+                $Y = $this->Y1;
+            }
+            if ($Y > $this->Y2) {
+                $Y = $this->Y2;
+            }
+
+            $this->Data[$Key]["X"] = $X;
+            $this->Data[$Key]["Y"] = $Y;
+        }
+
+        /* Dump all links */
+        $Links = [];
+        foreach ($this->Data as $Key => $Settings) {
+            $X1 = $Settings["X"];
+            $Y1 = $Settings["Y"];
+
+            if (isset($Settings["Connections"])) {
+                foreach ($Settings["Connections"] as $ID => $NodeID) {
+                    if (isset($this->Data[$NodeID])) {
+                        $X2 = $this->Data[$NodeID]["X"];
+                        $Y2 = $this->Data[$NodeID]["Y"];
+
+                        $Links[] = [
+                            "X1" => $X1,
+                            "Y1" => $Y1,
+                            "X2" => $X2,
+                            "Y2" => $Y2,
+                            "Source" => $Settings["Name"],
+                            "Destination" => $this->Data[$NodeID]["Name"]
+                        ];
+                    }
+                }
+            }
+        }
+
+        /* Check collisions */
+        $Conflicts = 0;
+        foreach ($this->Data as $Key => $Settings) {
+            $X1 = $Settings["X"];
+            $Y1 = $Settings["Y"];
+
+            if (isset($Settings["Connections"])) {
+                foreach ($Settings["Connections"] as $ID => $NodeID) {
+                    if (isset($this->Data[$NodeID])) {
+                        $X2 = $this->Data[$NodeID]["X"];
+                        $Y2 = $this->Data[$NodeID]["Y"];
+
+                        foreach ($Links as $IDLinks => $Link) {
+                            $X3 = $Link["X1"];
+                            $Y3 = $Link["Y1"];
+                            $X4 = $Link["X2"];
+                            $Y4 = $Link["Y2"];
+
+                            if (!($X1 == $X3 && $X2 == $X4 && $Y1 == $Y3 && $Y2 == $Y4)) {
+                                if ($this->intersect($X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4)) {
+                                    if ($Link["Source"] != $Settings["Name"]
+                                        && $Link["Source"] != $this->Data[$NodeID]["Name"]
+                                        && $Link["Destination"] != $Settings["Name"]
+                                        && $Link["Destination"] != $this->Data[$NodeID]["Name"]
+                                    ) {
+                                        $Conflicts++;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return $Conflicts / 2;
+    }
+
+    /**
+     * Center the graph
+     */
+    public function center()
+    {
+        /* Determine the real center */
+        $TargetCenterX = ($this->X2 - $this->X1) / 2 + $this->X1;
+        $TargetCenterY = ($this->Y2 - $this->Y1) / 2 + $this->Y1;
+
+        /* Get current boundaries */
+        $XMin = $this->X2;
+        $XMax = $this->X1;
+        $YMin = $this->Y2;
+        $YMax = $this->Y1;
+        foreach ($this->Data as $Key => $Settings) {
+            $X = $Settings["X"];
+            $Y = $Settings["Y"];
+
+            if ($X < $XMin) {
+                $XMin = $X;
+            }
+            if ($X > $XMax) {
+                $XMax = $X;
+            }
+            if ($Y < $YMin) {
+                $YMin = $Y;
+            }
+            if ($Y > $YMax) {
+                $YMax = $Y;
+            }
+        }
+        $CurrentCenterX = ($XMax - $XMin) / 2 + $XMin;
+        $CurrentCenterY = ($YMax - $YMin) / 2 + $YMin;
+
+        /* Compute the offset to apply */
+        $XOffset = $TargetCenterX - $CurrentCenterX;
+        $YOffset = $TargetCenterY - $CurrentCenterY;
+
+        /* Correct the points position */
+        foreach ($this->Data as $Key => $Settings) {
+            $this->Data[$Key]["X"] = $Settings["X"] + $XOffset;
+            $this->Data[$Key]["Y"] = $Settings["Y"] + $YOffset;
+        }
+    }
+
+    /**
+     * Create the encoded string
+     * @param Image $Object
+     * @param string $Settings
+     * @return array
+     */
+    public function drawSpring(Image $Object, array $Settings = [])
+    {
+        $this->pChartObject = $Object;
+
+        $Pass = isset($Settings["Pass"]) ? $Settings["Pass"] : 50;
+        $Retries = isset($Settings["Retry"]) ? $Settings["Retry"] : 10;
+        $this->MagneticForceA = isset($Settings["MagneticForceA"]) ? $Settings["MagneticForceA"] : 1.5;
+        $this->MagneticForceR = isset($Settings["MagneticForceR"]) ? $Settings["MagneticForceR"] : 2;
+        $this->RingSize = isset($Settings["RingSize"]) ? $Settings["RingSize"] : 40;
+        $DrawVectors = isset($Settings["DrawVectors"]) ? $Settings["DrawVectors"] : false;
+        $DrawQuietZone = isset($Settings["DrawQuietZone"]) ? $Settings["DrawQuietZone"] : false;
+        $CenterGraph = isset($Settings["CenterGraph"]) ? $Settings["CenterGraph"] : true;
+        $TextPadding = isset($Settings["TextPadding"]) ? $Settings["TextPadding"] : 4;
+        $Algorithm = isset($Settings["Algorithm"]) ? $Settings["Algorithm"] : ALGORITHM_WEIGHTED;
+
+        $this->X1 = $Object->GraphAreaX1;
+        $this->Y1 = $Object->GraphAreaY1;
+        $this->X2 = $Object->GraphAreaX2;
+        $this->Y2 = $Object->GraphAreaY2;
+
+        $Conflicts = 1;
+        $Jobs = 0;
+        $this->History["MinimumConflicts"] = -1;
+        while ($Conflicts != 0 && $Jobs < $Retries) {
+            $Jobs++;
+
+            /* Compute the initial settings */
+            $this->firstPass($Algorithm);
+
+            /* Apply the vectors */
+            if ($Pass > 0) {
+                for ($i = 0; $i <= $Pass; $i++) {
+                    $this->doPass();
+                }
+            }
+
+            $Conflicts = $this->lastPass();
+            if ($this->History["MinimumConflicts"] == -1
+                || $Conflicts < $this->History["MinimumConflicts"]
+            ) {
+                $this->History["MinimumConflicts"] = $Conflicts;
+                $this->History["Result"] = $this->Data;
+            }
+        }
+
+        $Conflicts = $this->History["MinimumConflicts"];
+        $this->Data = $this->History["Result"];
+
+        if ($CenterGraph) {
+            $this->center();
+        }
+
+        /* Draw the connections */
+        $Drawn = [];
+        foreach ($this->Data as $Key => $Settings) {
+            $X = $Settings["X"];
+            $Y = $Settings["Y"];
+
+            if (isset($Settings["Connections"])) {
+                foreach ($Settings["Connections"] as $ID => $NodeID) {
+                    if (!isset($Drawn[$Key])) {
+                        $Drawn[$Key] = "";
+                    }
+                    if (!isset($Drawn[$NodeID])) {
+                        $Drawn[$NodeID] = "";
+                    }
+
+                    if (isset($this->Data[$NodeID])
+                        && !isset($Drawn[$Key][$NodeID])
+                        && !isset($Drawn[$NodeID][$Key])
+                    ) {
+                        $Color = [
+                            "R" => $this->Default["LinkR"],
+                            "G" => $this->Default["LinkG"],
+                            "B" => $this->Default["LinkB"],
+                            "Alpha" => $this->Default["Alpha"]
+                        ];
+
+                        if (count($this->Links)) {
+                            if (isset($this->Links[$Key][$NodeID]["R"])) {
+                                $Color = [
+                                    "R" => $this->Links[$Key][$NodeID]["R"],
+                                    "G" => $this->Links[$Key][$NodeID]["G"],
+                                    "B" => $this->Links[$Key][$NodeID]["B"],
+                                    "Alpha" => $this->Links[$Key][$NodeID]["Alpha"]
+                                ];
+                            }
+
+                            if (isset($this->Links[$Key][$NodeID]["Ticks"])) {
+                                $Color["Ticks"] = $this->Links[$Key][$NodeID]["Ticks"];
+                            }
+                        }
+
+                        $X2 = $this->Data[$NodeID]["X"];
+                        $Y2 = $this->Data[$NodeID]["Y"];
+                        $this->pChartObject->drawLine($X, $Y, $X2, $Y2, $Color);
+                        $Drawn[$Key][$NodeID] = true;
+
+                        if (isset($this->Links) && count($this->Links)) {
+                            if (isset($this->Links[$Key][$NodeID]["Name"])
+                                || isset($this->Links[$NodeID][$Key]["Name"])
+                            ) {
+                                $Name = isset($this->Links[$Key][$NodeID]["Name"])
+                                    ? $this->Links[$Key][$NodeID]["Name"]
+                                    : $this->Links[$NodeID][$Key]["Name"]
+                                ;
+                                $TxtX = ($X2 - $X) / 2 + $X;
+                                $TxtY = ($Y2 - $Y) / 2 + $Y;
+
+                                if ($X <= $X2) {
+                                    $Angle = (360 - $this->getAngle($X, $Y, $X2, $Y2)) % 360;
+                                } else {
+                                    $Angle = (360 - $this->getAngle($X2, $Y2, $X, $Y)) % 360;
+                                }
+                                $Settings = $Color;
+                                $Settings["Angle"] = $Angle;
+                                $Settings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
+                                $this->pChartObject->drawText($TxtX, $TxtY, $Name, $Settings);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /* Draw the quiet zones */
+        if ($DrawQuietZone) {
+            foreach ($this->Data as $Key => $Settings) {
+                $X = $Settings["X"];
+                $Y = $Settings["Y"];
+                $FreeZone = $Settings["FreeZone"];
+
+                $this->pChartObject->drawFilledCircle(
+                    $X,
+                    $Y,
+                    $FreeZone,
+                    ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 2]
+                );
+            }
+        }
+
+
+        /* Draw the nodes */
+        foreach ($this->Data as $Key => $Settings) {
+            $X = $Settings["X"];
+            $Y = $Settings["Y"];
+            $Name = $Settings["Name"];
+            $FreeZone = $Settings["FreeZone"];
+            $Shape = $Settings["Shape"];
+            $Size = $Settings["Size"];
+
+            $Color = [
+                "R" => $Settings["R"],
+                "G" => $Settings["G"],
+                "B" => $Settings["B"],
+                "Alpha" => $Settings["Alpha"],
+                "BorderR" => $Settings["BorderR"],
+                "BorderG" => $Settings["BorderG"],
+                "BorderB" => $Settings["BorderB"],
+                "BorderApha" => $Settings["BorderAlpha"]
+            ];
+
+            if ($Shape == NODE_SHAPE_CIRCLE) {
+                $this->pChartObject->drawFilledCircle($X, $Y, $Size, $Color);
+            } elseif ($Shape == NODE_SHAPE_TRIANGLE) {
+                $Points = [];
+                $Points[] = cos(deg2rad(270)) * $Size + $X;
+                $Points[] = sin(deg2rad(270)) * $Size + $Y;
+                $Points[] = cos(deg2rad(45)) * $Size + $X;
+                $Points[] = sin(deg2rad(45)) * $Size + $Y;
+                $Points[] = cos(deg2rad(135)) * $Size + $X;
+                $Points[] = sin(deg2rad(135)) * $Size + $Y;
+                $this->pChartObject->drawPolygon($Points, $Color);
+            } elseif ($Shape == NODE_SHAPE_SQUARE) {
+                $Offset = $Size / 2;
+                $Size = $Size / 2;
+                $this->pChartObject->drawFilledRectangle(
+                    $X - $Offset,
+                    $Y - $Offset,
+                    $X + $Offset,
+                    $Y + $Offset,
+                    $Color
+                );
+            }
+
+            if ($Name != "") {
+                $LabelOptions = [
+                    "R" => $this->Labels["R"],
+                    "G" => $this->Labels["G"],
+                    "B" => $this->Labels["B"],
+                    "Alpha" => $this->Labels["Alpha"]
+                ];
+
+                if ($this->Labels["Type"] == LABEL_LIGHT) {
+                    $LabelOptions["Align"] = TEXT_ALIGN_BOTTOMLEFT;
+                    $this->pChartObject->drawText($X, $Y, $Name, $LabelOptions);
+                } elseif ($this->Labels["Type"] == LABEL_CLASSIC) {
+                    $LabelOptions["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                    $LabelOptions["DrawBox"] = true;
+                    $LabelOptions["BoxAlpha"] = 50;
+                    $LabelOptions["BorderOffset"] = 4;
+                    $LabelOptions["RoundedRadius"] = 3;
+                    $LabelOptions["BoxRounded"] = true;
+                    $LabelOptions["NoShadow"] = true;
+
+                    $this->pChartObject->drawText($X, $Y + $Size + $TextPadding, $Name, $LabelOptions);
+                }
+            }
+        }
+
+        /* Draw the vectors */
+        if ($DrawVectors) {
+            foreach ($this->Data as $Key => $Settings) {
+                $X1 = $Settings["X"];
+                $Y1 = $Settings["Y"];
+
+                if (isset($Settings["Vectors"]) && $Settings["Type"] != NODE_TYPE_CENTRAL) {
+                    foreach ($Settings["Vectors"] as $ID => $Vector) {
+                        $Type = $Vector["Type"];
+                        $Force = $Vector["Force"];
+                        $Angle = $Vector["Angle"];
+                        $Factor = $Type == "A" ? $this->MagneticForceA : $this->MagneticForceR;
+                        $Color = $Type == "A"
+                            ? ["FillR" => 255, "FillG" => 0, "FillB" => 0]
+                            : ["FillR" => 0, "FillG" => 255, "FillB" => 0]
+                        ;
+
+                        $X2 = cos(deg2rad($Angle)) * $Force * $Factor + $X1;
+                        $Y2 = sin(deg2rad($Angle)) * $Force * $Factor + $Y1;
+
+                        $this->pChartObject->drawArrow($X1, $Y1, $X2, $Y2, $Color);
+                    }
+                }
+            }
+        }
+
+        return ["Pass" => $Jobs, "Conflicts" => $Conflicts];
+    }
+
+    /**
+     * Return the distance between two points
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @return int|float
+     */
+    public function getDistance($X1, $Y1, $X2, $Y2)
+    {
+        return sqrt(($X2 - $X1) * ($X2 - $X1) + ($Y2 - $Y1) * ($Y2 - $Y1));
+    }
+
+    /**
+     * Return the angle made by a line and the X axis
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @return int|float
+     */
+    public function getAngle($X1, $Y1, $X2, $Y2)
+    {
+        $Opposite = $Y2 - $Y1;
+        $Adjacent = $X2 - $X1;
+        $Angle = rad2deg(atan2($Opposite, $Adjacent));
+
+        return $Angle > 0 ? $Angle : 360 - abs($Angle);
+    }
+
+    /**
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param int $X3
+     * @param int $Y3
+     * @param int $X4
+     * @param int $Y4
+     * @return boolean
+     */
+    public function intersect($X1, $Y1, $X2, $Y2, $X3, $Y3, $X4, $Y4)
+    {
+        $A = (($X3 * $Y4 - $X4 * $Y3) * ($X1 - $X2) - ($X1 * $Y2 - $X2 * $Y1) * ($X3 - $X4));
+        $B = (($Y1 - $Y2) * ($X3 - $X4) - ($Y3 - $Y4) * ($X1 - $X2));
+
+        if ($B == 0) {
+            return false;
+        }
+        $Xi = $A / $B;
+
+        $C = ($X1 - $X2);
+        if ($C == 0) {
+            return false;
+        }
+        $Yi = $Xi * (($Y1 - $Y2) / $C) + (($X1 * $Y2 - $X2 * $Y1) / $C);
+
+        if ($Xi >= min($X1, $X2)
+            && $Xi >= min($X3, $X4)
+            && $Xi <= max($X1, $X2)
+            && $Xi <= max($X3, $X4)
+            && $Yi >= min($Y1, $Y2)
+            && $Yi >= min($Y3, $Y4)
+            && $Yi <= max($Y1, $Y2)
+            && $Yi <= max($Y3, $Y4)
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 453 - 0
vendor/szymach/c-pchart/src/Chart/Stock.php

@@ -0,0 +1,453 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Data;
+use CpChart\Image;
+
+/**
+ *  Stock - class to draw stock charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Stock
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var Data
+     */
+    public $pDataObject;
+
+    /**
+     * @param Image $pChartObject
+     * @param Data $pDataObject
+     */
+    public function __construct(Image $pChartObject, Data $pDataObject)
+    {
+        $this->pChartObject = $pChartObject;
+        $this->pDataObject = $pDataObject;
+    }
+
+    /**
+     * Draw a stock chart
+     * @param array $Format
+     * @return integer|null
+     */
+    public function drawStockChart(array $Format = [])
+    {
+        $SerieOpen = isset($Format["SerieOpen"]) ? $Format["SerieOpen"] : "Open";
+        $SerieClose = isset($Format["SerieClose"]) ? $Format["SerieClose"] : "Close";
+        $SerieMin = isset($Format["SerieMin"]) ? $Format["SerieMin"] : "Min";
+        $SerieMax = isset($Format["SerieMax"]) ? $Format["SerieMax"] : "Max";
+        $SerieMedian = isset($Format["SerieMedian"]) ? $Format["SerieMedian"] : null;
+        $LineWidth = isset($Format["LineWidth"]) ? $Format["LineWidth"] : 1;
+        $LineR = isset($Format["LineR"]) ? $Format["LineR"] : 0;
+        $LineG = isset($Format["LineG"]) ? $Format["LineG"] : 0;
+        $LineB = isset($Format["LineB"]) ? $Format["LineB"] : 0;
+        $LineAlpha = isset($Format["LineAlpha"]) ? $Format["LineAlpha"] : 100;
+        $ExtremityWidth = isset($Format["ExtremityWidth"]) ? $Format["ExtremityWidth"] : 1;
+        $ExtremityLength = isset($Format["ExtremityLength"]) ? $Format["ExtremityLength"] : 3;
+        $ExtremityR = isset($Format["ExtremityR"]) ? $Format["ExtremityR"] : 0;
+        $ExtremityG = isset($Format["ExtremityG"]) ? $Format["ExtremityG"] : 0;
+        $ExtremityB = isset($Format["ExtremityB"]) ? $Format["ExtremityB"] : 0;
+        $ExtremityAlpha = isset($Format["ExtremityAlpha"]) ? $Format["ExtremityAlpha"] : 100;
+        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 8;
+        $BoxUpR = isset($Format["BoxUpR"]) ? $Format["BoxUpR"] : 188;
+        $BoxUpG = isset($Format["BoxUpG"]) ? $Format["BoxUpG"] : 224;
+        $BoxUpB = isset($Format["BoxUpB"]) ? $Format["BoxUpB"] : 46;
+        $BoxUpAlpha = isset($Format["BoxUpAlpha"]) ? $Format["BoxUpAlpha"] : 100;
+        $BoxUpSurrounding = isset($Format["BoxUpSurrounding"]) ? $Format["BoxUpSurrounding"] : null;
+        $BoxUpBorderR = isset($Format["BoxUpBorderR"]) ? $Format["BoxUpBorderR"] : $BoxUpR - 20;
+        $BoxUpBorderG = isset($Format["BoxUpBorderG"]) ? $Format["BoxUpBorderG"] : $BoxUpG - 20;
+        $BoxUpBorderB = isset($Format["BoxUpBorderB"]) ? $Format["BoxUpBorderB"] : $BoxUpB - 20;
+        $BoxUpBorderAlpha = isset($Format["BoxUpBorderAlpha"]) ? $Format["BoxUpBorderAlpha"] : 100;
+        $BoxDownR = isset($Format["BoxDownR"]) ? $Format["BoxDownR"] : 224;
+        $BoxDownG = isset($Format["BoxDownG"]) ? $Format["BoxDownG"] : 100;
+        $BoxDownB = isset($Format["BoxDownB"]) ? $Format["BoxDownB"] : 46;
+        $BoxDownAlpha = isset($Format["BoxDownAlpha"]) ? $Format["BoxDownAlpha"] : 100;
+        $BoxDownSurrounding = isset($Format["BoxDownSurrounding"]) ? $Format["BoxDownSurrounding"] : null;
+        $BoxDownBorderR = isset($Format["BoxDownBorderR"]) ? $Format["BoxDownBorderR"] : $BoxDownR - 20;
+        $BoxDownBorderG = isset($Format["BoxDownBorderG"]) ? $Format["BoxDownBorderG"] : $BoxDownG - 20;
+        $BoxDownBorderB = isset($Format["BoxDownBorderB"]) ? $Format["BoxDownBorderB"] : $BoxDownB - 20;
+        $BoxDownBorderAlpha = isset($Format["BoxDownBorderAlpha"]) ? $Format["BoxDownBorderAlpha"] : 100;
+        $ShadowOnBoxesOnly = isset($Format["ShadowOnBoxesOnly"]) ? $Format["ShadowOnBoxesOnly"] : true;
+        $MedianR = isset($Format["MedianR"]) ? $Format["MedianR"] : 255;
+        $MedianG = isset($Format["MedianG"]) ? $Format["MedianG"] : 0;
+        $MedianB = isset($Format["MedianB"]) ? $Format["MedianB"] : 0;
+        $MedianAlpha = isset($Format["MedianAlpha"]) ? $Format["MedianAlpha"] : 100;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapTitle = isset($Format["ImageMapTitle"]) ? $Format["ImageMapTitle"] : "Stock Chart";
+
+        /* Data Processing */
+        if ($BoxUpSurrounding != null) {
+            $BoxUpBorderR = $BoxUpR + $BoxUpSurrounding;
+            $BoxUpBorderG = $BoxUpG + $BoxUpSurrounding;
+            $BoxUpBorderB = $BoxUpB + $BoxUpSurrounding;
+        }
+        if ($BoxDownSurrounding != null) {
+            $BoxDownBorderR = $BoxDownR + $BoxDownSurrounding;
+            $BoxDownBorderG = $BoxDownG + $BoxDownSurrounding;
+            $BoxDownBorderB = $BoxDownB + $BoxDownSurrounding;
+        }
+
+        if ($LineWidth != 1) {
+            $LineOffset = $LineWidth / 2;
+        }
+        $BoxOffset = $BoxWidth / 2;
+
+        $Data = $this->pChartObject->DataSet->getData();
+        list($XMargin, $XDivs) = $this->pChartObject->scaleGetXSettings();
+
+        if (!isset($Data["Series"][$SerieOpen])
+            || !isset($Data["Series"][$SerieClose])
+            || !isset($Data["Series"][$SerieMin])
+            || !isset($Data["Series"][$SerieMax])
+        ) {
+            return STOCK_MISSING_SERIE;
+        }
+        $Plots = [];
+        foreach ($Data["Series"][$SerieOpen]["Data"] as $Key => $Value) {
+            $Point = [];
+            if (isset($Data["Series"][$SerieClose]["Data"][$Key])
+                || isset($Data["Series"][$SerieMin]["Data"][$Key])
+                || isset($Data["Series"][$SerieMax]["Data"][$Key])
+            ) {
+                $Point = [
+                    $Value,
+                    $Data["Series"][$SerieClose]["Data"][$Key],
+                    $Data["Series"][$SerieMin]["Data"][$Key],
+                    $Data["Series"][$SerieMax]["Data"][$Key]
+                ];
+            }
+            if ($SerieMedian != null && isset($Data["Series"][$SerieMedian]["Data"][$Key])) {
+                $Point[] = $Data["Series"][$SerieMedian]["Data"][$Key];
+            }
+            $Plots[] = $Point;
+        }
+
+        $AxisID = $Data["Series"][$SerieOpen]["Axis"];
+        $Format = $Data["Axis"][$AxisID]["Format"];
+
+        $YZero = $this->pChartObject->scaleComputeY(0, ["AxisID" => $AxisID]);
+        $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2) / $XDivs;
+
+        $X = $this->pChartObject->GraphAreaX1 + $XMargin;
+        $Y = $this->pChartObject->GraphAreaY1 + $XMargin;
+
+        $LineSettings = ["R" => $LineR, "G" => $LineG, "B" => $LineB, "Alpha" => $LineAlpha];
+        $ExtremitySettings = [
+            "R" => $ExtremityR,
+            "G" => $ExtremityG,
+            "B" => $ExtremityB,
+            "Alpha" => $ExtremityAlpha
+        ];
+        $BoxUpSettings = [
+            "R" => $BoxUpR,
+            "G" => $BoxUpG,
+            "B" => $BoxUpB,
+            "Alpha" => $BoxUpAlpha,
+            "BorderR" => $BoxUpBorderR,
+            "BorderG" => $BoxUpBorderG,
+            "BorderB" => $BoxUpBorderB,
+            "BorderAlpha" => $BoxUpBorderAlpha
+        ];
+        $BoxDownSettings = [
+            "R" => $BoxDownR,
+            "G" => $BoxDownG,
+            "B" => $BoxDownB,
+            "Alpha" => $BoxDownAlpha,
+            "BorderR" => $BoxDownBorderR,
+            "BorderG" => $BoxDownBorderG,
+            "BorderB" => $BoxDownBorderB,
+            "BorderAlpha" => $BoxDownBorderAlpha
+        ];
+        $MedianSettings = ["R" => $MedianR, "G" => $MedianG, "B" => $MedianB, "Alpha" => $MedianAlpha];
+
+        foreach ($Plots as $Key => $Points) {
+            $PosArray = $this->pChartObject->scaleComputeY($Points, ["AxisID" => $AxisID]);
+
+            $Values = "Open :" . $Data["Series"][$SerieOpen]["Data"][$Key]
+                    . "<BR>Close : " . $Data["Series"][$SerieClose]["Data"][$Key]
+                    . "<BR>Min : " . $Data["Series"][$SerieMin]["Data"][$Key]
+                    . "<BR>Max : " . $Data["Series"][$SerieMax]["Data"][$Key] . "<BR>";
+            if ($SerieMedian != null) {
+                $Values = $Values . "Median : " . $Data["Series"][$SerieMedian]["Data"][$Key] . "<BR>";
+            }
+            if ($PosArray[0] > $PosArray[1]) {
+                $ImageMapColor = $this->pChartObject->toHTMLColor($BoxUpR, $BoxUpG, $BoxUpB);
+            } else {
+                $ImageMapColor = $this->pChartObject->toHTMLColor($BoxDownR, $BoxDownG, $BoxDownB);
+            }
+
+            if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                if ($YZero > $this->pChartObject->GraphAreaY2 - 1) {
+                    $YZero = $this->pChartObject->GraphAreaY2 - 1;
+                }
+                if ($YZero < $this->pChartObject->GraphAreaY1 + 1) {
+                    $YZero = $this->pChartObject->GraphAreaY1 + 1;
+                }
+
+                if ($XDivs == 0) {
+                    $XStep = 0;
+                } else {
+                    $XStep = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1 - $XMargin * 2)
+                        / $XDivs
+                    ;
+                }
+
+                if ($ShadowOnBoxesOnly) {
+                    $RestoreShadow = $this->pChartObject->Shadow;
+                    $this->pChartObject->Shadow = false;
+                }
+
+                if ($LineWidth == 1) {
+                    $this->pChartObject->drawLine($X, $PosArray[2], $X, $PosArray[3], $LineSettings);
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - $LineOffset,
+                        $PosArray[2],
+                        $X + $LineOffset,
+                        $PosArray[3],
+                        $LineSettings
+                    );
+                }
+
+                if ($ExtremityWidth == 1) {
+                    $this->pChartObject->drawLine(
+                        $X - $ExtremityLength,
+                        $PosArray[2],
+                        $X + $ExtremityLength,
+                        $PosArray[2],
+                        $ExtremitySettings
+                    );
+                    $this->pChartObject->drawLine(
+                        $X - $ExtremityLength,
+                        $PosArray[3],
+                        $X + $ExtremityLength,
+                        $PosArray[3],
+                        $ExtremitySettings
+                    );
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "RECT",
+                            sprintf(
+                                "%s,%s,%s,%s",
+                                floor($X - $ExtremityLength),
+                                floor($PosArray[2]),
+                                floor($X + $ExtremityLength),
+                                floor($PosArray[3])
+                            ),
+                            $ImageMapColor,
+                            $ImageMapTitle,
+                            $Values
+                        );
+                    }
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - $ExtremityLength,
+                        $PosArray[2],
+                        $X + $ExtremityLength,
+                        $PosArray[2] - $ExtremityWidth,
+                        $ExtremitySettings
+                    );
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - $ExtremityLength,
+                        $PosArray[3],
+                        $X + $ExtremityLength,
+                        $PosArray[3] + $ExtremityWidth,
+                        $ExtremitySettings
+                    );
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "RECT",
+                            sprintf(
+                                "%s,%s,%s,%s",
+                                floor($X - $ExtremityLength),
+                                floor($PosArray[2] - $ExtremityWidth),
+                                floor($X + $ExtremityLength),
+                                floor($PosArray[3] + $ExtremityWidth)
+                            ),
+                            $ImageMapColor,
+                            $ImageMapTitle,
+                            $Values
+                        );
+                    }
+                }
+
+                if ($ShadowOnBoxesOnly) {
+                    $this->pChartObject->Shadow = $RestoreShadow;
+                }
+
+                if ($PosArray[0] > $PosArray[1]) {
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - $BoxOffset,
+                        $PosArray[0],
+                        $X + $BoxOffset,
+                        $PosArray[1],
+                        $BoxUpSettings
+                    );
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $X - $BoxOffset,
+                        $PosArray[0],
+                        $X + $BoxOffset,
+                        $PosArray[1],
+                        $BoxDownSettings
+                    );
+                }
+
+                if (isset($PosArray[4])) {
+                    $this->pChartObject->drawLine(
+                        $X - $ExtremityLength,
+                        $PosArray[4],
+                        $X + $ExtremityLength,
+                        $PosArray[4],
+                        $MedianSettings
+                    );
+                }
+                $X = $X + $XStep;
+            } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+                if ($YZero > $this->pChartObject->GraphAreaX2 - 1) {
+                    $YZero = $this->pChartObject->GraphAreaX2 - 1;
+                }
+                if ($YZero < $this->pChartObject->GraphAreaX1 + 1) {
+                    $YZero = $this->pChartObject->GraphAreaX1 + 1;
+                }
+
+                if ($XDivs == 0) {
+                    $XStep = 0;
+                } else {
+                    $XStep = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1 - $XMargin * 2)
+                        / $XDivs
+                    ;
+                }
+
+                if ($LineWidth == 1) {
+                    $this->pChartObject->drawLine($PosArray[2], $Y, $PosArray[3], $Y, $LineSettings);
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $PosArray[2],
+                        $Y - $LineOffset,
+                        $PosArray[3],
+                        $Y + $LineOffset,
+                        $LineSettings
+                    );
+                }
+                if ($ShadowOnBoxesOnly) {
+                    $RestoreShadow = $this->pChartObject->Shadow;
+                    $this->pChartObject->Shadow = false;
+                }
+
+                if ($ExtremityWidth == 1) {
+                    $this->pChartObject->drawLine(
+                        $PosArray[2],
+                        $Y - $ExtremityLength,
+                        $PosArray[2],
+                        $Y + $ExtremityLength,
+                        $ExtremitySettings
+                    );
+                    $this->pChartObject->drawLine(
+                        $PosArray[3],
+                        $Y - $ExtremityLength,
+                        $PosArray[3],
+                        $Y + $ExtremityLength,
+                        $ExtremitySettings
+                    );
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "RECT",
+                            sprintf(
+                                "%s,%s,%s,%s",
+                                floor($PosArray[2]),
+                                floor($Y - $ExtremityLength),
+                                floor($PosArray[3]),
+                                floor($Y + $ExtremityLength)
+                            ),
+                            $ImageMapColor,
+                            $ImageMapTitle,
+                            $Values
+                        );
+                    }
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $PosArray[2],
+                        $Y - $ExtremityLength,
+                        $PosArray[2] - $ExtremityWidth,
+                        $Y + $ExtremityLength,
+                        $ExtremitySettings
+                    );
+                    $this->pChartObject->drawFilledRectangle(
+                        $PosArray[3],
+                        $Y - $ExtremityLength,
+                        $PosArray[3] + $ExtremityWidth,
+                        $Y + $ExtremityLength,
+                        $ExtremitySettings
+                    );
+
+                    if ($RecordImageMap) {
+                        $this->pChartObject->addToImageMap(
+                            "RECT",
+                            sprintf(
+                                "%s,%s,%s,%s",
+                                floor($PosArray[2] - $ExtremityWidth),
+                                floor($Y - $ExtremityLength),
+                                floor($PosArray[3] + $ExtremityWidth),
+                                floor($Y + $ExtremityLength)
+                            ),
+                            $ImageMapColor,
+                            $ImageMapTitle,
+                            $Values
+                        );
+                    }
+                }
+
+                if ($ShadowOnBoxesOnly) {
+                    $this->pChartObject->Shadow = $RestoreShadow;
+                }
+
+                if ($PosArray[0] < $PosArray[1]) {
+                    $this->pChartObject->drawFilledRectangle(
+                        $PosArray[0],
+                        $Y - $BoxOffset,
+                        $PosArray[1],
+                        $Y + $BoxOffset,
+                        $BoxUpSettings
+                    );
+                } else {
+                    $this->pChartObject->drawFilledRectangle(
+                        $PosArray[0],
+                        $Y - $BoxOffset,
+                        $PosArray[1],
+                        $Y + $BoxOffset,
+                        $BoxDownSettings
+                    );
+                }
+                if (isset($PosArray[4])) {
+                    $this->pChartObject->drawLine(
+                        $PosArray[4],
+                        $Y - $ExtremityLength,
+                        $PosArray[4],
+                        $Y + $ExtremityLength,
+                        $MedianSettings
+                    );
+                }
+                $Y = $Y + $XStep;
+            }
+        }
+    }
+}

+ 414 - 0
vendor/szymach/c-pchart/src/Chart/Surface.php

@@ -0,0 +1,414 @@
+<?php
+
+namespace CpChart\Chart;
+
+use CpChart\Image;
+
+/**
+ *  Surface - class to draw surface charts
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Surface
+{
+    /**
+     * @var Image
+     */
+    public $pChartObject;
+
+    /**
+     * @var int
+     */
+    public $GridSizeX;
+
+    /**
+     * @var int
+     */
+    public $GridSizeY;
+
+    /**
+     * @var array
+     */
+    public $Points = [];
+
+    /**
+     * @param Image $pChartObject
+     */
+    public function __construct(Image $pChartObject)
+    {
+        $this->pChartObject = $pChartObject;
+    }
+
+    /**
+     * Define the grid size and initialise the 2D matrix
+     * @param int $XSize
+     * @param int $YSize
+     */
+    public function setGrid($XSize = 10, $YSize = 10)
+    {
+        for ($X = 0; $X <= $XSize; $X++) {
+            for ($Y = 0; $Y <= $YSize; $Y++) {
+                $this->Points[$X][$Y] = UNKNOWN;
+            }
+        }
+
+        $this->GridSizeX = $XSize;
+        $this->GridSizeY = $YSize;
+    }
+
+    /**
+     * Add a point on the grid
+     * @param int $X
+     * @param int $Y
+     * @param int|float $Value
+     * @param boolean $Force
+     * @return null
+     */
+    public function addPoint($X, $Y, $Value, $Force = true)
+    {
+        if ($X < 0 || $X > $this->GridSizeX) {
+            return 0;
+        }
+        if ($Y < 0 || $Y > $this->GridSizeY) {
+            return 0;
+        }
+
+        if ($this->Points[$X][$Y] == UNKNOWN || $Force) {
+            $this->Points[$X][$Y] = $Value;
+        } elseif ($this->Points[$X][$Y] == UNKNOWN) {
+            $this->Points[$X][$Y] = $Value;
+        } else {
+            $this->Points[$X][$Y] = ($this->Points[$X][$Y] + $Value) / 2;
+        }
+    }
+
+    /**
+     * Write the X labels
+     * @param array $Format
+     * @return null|int
+     */
+    public function writeXLabels(array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : $this->pChartObject->FontColorR;
+        $G = isset($Format["G"]) ? $Format["G"] : $this->pChartObject->FontColorG;
+        $B = isset($Format["B"]) ? $Format["B"] : $this->pChartObject->FontColorB;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $this->pChartObject->FontColorA;
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 5;
+        $Position = isset($Format["Position"]) ? $Format["Position"] : LABEL_POSITION_TOP;
+        $Labels = isset($Format["Labels"]) ? $Format["Labels"] : null;
+        $CountOffset = isset($Format["CountOffset"]) ? $Format["CountOffset"] : 0;
+
+        if ($Labels != null && !is_array($Labels)) {
+            $Label = $Labels;
+            $Labels = [$Label];
+        }
+
+        $X0 = $this->pChartObject->GraphAreaX1;
+        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
+
+        $Settings = ["Angle" => $Angle, "R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+        if ($Position == LABEL_POSITION_TOP) {
+            $YPos = $this->pChartObject->GraphAreaY1 - $Padding;
+            if ($Angle == 0) {
+                $Settings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
+            }
+            if ($Angle != 0) {
+                $Settings["Align"] = TEXT_ALIGN_MIDDLELEFT;
+            }
+        } elseif ($Position == LABEL_POSITION_BOTTOM) {
+            $YPos = $this->pChartObject->GraphAreaY2 + $Padding;
+            if ($Angle == 0) {
+                $Settings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+            }
+            if ($Angle != 0) {
+                $Settings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
+            }
+        } else {
+            return -1;
+        }
+        for ($X = 0; $X <= $this->GridSizeX; $X++) {
+            $XPos = floor($X0 + $X * $XSize + $XSize / 2);
+
+            if ($Labels == null || !isset($Labels[$X])) {
+                $Value = $X + $CountOffset;
+            } else {
+                $Value = $Labels[$X];
+            }
+            $this->pChartObject->drawText($XPos, $YPos, $Value, $Settings);
+        }
+    }
+
+    /**
+     * Write the Y labels
+     * @param array $Format
+     * @return type
+     */
+    public function writeYLabels(array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : $this->pChartObject->FontColorR;
+        $G = isset($Format["G"]) ? $Format["G"] : $this->pChartObject->FontColorG;
+        $B = isset($Format["B"]) ? $Format["B"] : $this->pChartObject->FontColorB;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $this->pChartObject->FontColorA;
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 5;
+        $Position = isset($Format["Position"]) ? $Format["Position"] : LABEL_POSITION_LEFT;
+        $Labels = isset($Format["Labels"]) ? $Format["Labels"] : null;
+        $CountOffset = isset($Format["CountOffset"]) ? $Format["CountOffset"] : 0;
+
+        if ($Labels != null && !is_array($Labels)) {
+            $Label = $Labels;
+            $Labels = [$Label];
+        }
+
+        $Y0 = $this->pChartObject->GraphAreaY1;
+        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
+
+        $Settings = ["Angle" => $Angle, "R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+        if ($Position == LABEL_POSITION_LEFT) {
+            $XPos = $this->pChartObject->GraphAreaX1 - $Padding;
+            $Settings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
+        } elseif ($Position == LABEL_POSITION_RIGHT) {
+            $XPos = $this->pChartObject->GraphAreaX2 + $Padding;
+            $Settings["Align"] = TEXT_ALIGN_MIDDLELEFT;
+        } else {
+            return -1;
+        }
+        for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
+            $YPos = floor($Y0 + $Y * $YSize + $YSize / 2);
+
+            if ($Labels == null || !isset($Labels[$Y])) {
+                $Value = $Y + $CountOffset;
+            } else {
+                $Value = $Labels[$Y];
+            }
+            $this->pChartObject->drawText($XPos, $YPos, $Value, $Settings);
+        }
+    }
+
+    /**
+     * Draw the area arround the specified Threshold
+     * @param int|float $Threshold
+     * @param array $Format
+     */
+    public function drawContour($Threshold, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 3;
+        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 0;
+
+        $X0 = $this->pChartObject->GraphAreaX1;
+        $Y0 = $this->pChartObject->GraphAreaY1;
+        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
+        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
+
+        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks];
+
+        for ($X = 0; $X <= $this->GridSizeX; $X++) {
+            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
+                $Value = $this->Points[$X][$Y];
+
+                if ($Value != UNKNOWN && $Value != IGNORED && $Value >= $Threshold) {
+                    $X1 = floor($X0 + $X * $XSize) + $Padding;
+                    $Y1 = floor($Y0 + $Y * $YSize) + $Padding;
+                    $X2 = floor($X0 + $X * $XSize + $XSize);
+                    $Y2 = floor($Y0 + $Y * $YSize + $YSize);
+
+                    if ($X > 0 && $this->Points[$X - 1][$Y] != UNKNOWN
+                        && $this->Points[$X - 1][$Y] != IGNORED
+                        && $this->Points[$X - 1][$Y] < $Threshold
+                    ) {
+                        $this->pChartObject->drawLine($X1, $Y1, $X1, $Y2, $Color);
+                    }
+                    if ($Y > 0 && $this->Points[$X][$Y - 1] != UNKNOWN
+                        && $this->Points[$X][$Y - 1] != IGNORED
+                        && $this->Points[$X][$Y - 1] < $Threshold
+                    ) {
+                        $this->pChartObject->drawLine($X1, $Y1, $X2, $Y1, $Color);
+                    }
+                    if ($X < $this->GridSizeX
+                        && $this->Points[$X + 1][$Y] != UNKNOWN
+                        && $this->Points[$X + 1][$Y] != IGNORED
+                        && $this->Points[$X + 1][$Y] < $Threshold
+                    ) {
+                        $this->pChartObject->drawLine($X2, $Y1, $X2, $Y2, $Color);
+                    }
+                    if ($Y < $this->GridSizeY
+                        && $this->Points[$X][$Y + 1] != UNKNOWN
+                        && $this->Points[$X][$Y + 1] != IGNORED
+                        && $this->Points[$X][$Y + 1] < $Threshold
+                    ) {
+                        $this->pChartObject->drawLine($X1, $Y2, $X2, $Y2, $Color);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw the surface chart
+     * @param array $Format
+     */
+    public function drawSurface(array $Format = [])
+    {
+        $Palette = isset($Format["Palette"]) ? $Format["Palette"] : null;
+        $ShadeR1 = isset($Format["ShadeR1"]) ? $Format["ShadeR1"] : 77;
+        $ShadeG1 = isset($Format["ShadeG1"]) ? $Format["ShadeG1"] : 205;
+        $ShadeB1 = isset($Format["ShadeB1"]) ? $Format["ShadeB1"] : 21;
+        $ShadeA1 = isset($Format["ShadeA1"]) ? $Format["ShadeA1"] : 40;
+        $ShadeR2 = isset($Format["ShadeR2"]) ? $Format["ShadeR2"] : 227;
+        $ShadeG2 = isset($Format["ShadeG2"]) ? $Format["ShadeG2"] : 135;
+        $ShadeB2 = isset($Format["ShadeB2"]) ? $Format["ShadeB2"] : 61;
+        $ShadeA2 = isset($Format["ShadeA2"]) ? $Format["ShadeA2"] : 100;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 0;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 0;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 0;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : -1;
+        $Padding = isset($Format["Padding"]) ? $Format["Padding"] : 1;
+
+        $X0 = $this->pChartObject->GraphAreaX1;
+        $Y0 = $this->pChartObject->GraphAreaY1;
+        $XSize = ($this->pChartObject->GraphAreaX2 - $this->pChartObject->GraphAreaX1) / ($this->GridSizeX + 1);
+        $YSize = ($this->pChartObject->GraphAreaY2 - $this->pChartObject->GraphAreaY1) / ($this->GridSizeY + 1);
+
+        for ($X = 0; $X <= $this->GridSizeX; $X++) {
+            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
+                $Value = $this->Points[$X][$Y];
+
+                if ($Value != UNKNOWN && $Value != IGNORED) {
+                    $X1 = floor($X0 + $X * $XSize) + $Padding;
+                    $Y1 = floor($Y0 + $Y * $YSize) + $Padding;
+                    $X2 = floor($X0 + $X * $XSize + $XSize);
+                    $Y2 = floor($Y0 + $Y * $YSize + $YSize);
+
+                    if ($Palette != null) {
+                        if (isset($Palette[$Value]) && isset($Palette[$Value]["R"])) {
+                            $R = $Palette[$Value]["R"];
+                        } else {
+                            $R = 0;
+                        }
+
+                        if (isset($Palette[$Value]) && isset($Palette[$Value]["G"])) {
+                            $G = $Palette[$Value]["G"];
+                        } else {
+                            $G = 0;
+                        }
+
+                        if (isset($Palette[$Value]) && isset($Palette[$Value]["B"])) {
+                            $B = $Palette[$Value]["B"];
+                        } else {
+                            $B = 0;
+                        }
+                        if (isset($Palette[$Value]) && isset($Palette[$Value]["Alpha"])) {
+                            $Alpha = $Palette[$Value]["Alpha"];
+                        } else {
+                            $Alpha = 1000;
+                        }
+                    } else {
+                        $R = (($ShadeR2 - $ShadeR1) / 100) * $Value + $ShadeR1;
+                        $G = (($ShadeG2 - $ShadeG1) / 100) * $Value + $ShadeG1;
+                        $B = (($ShadeB2 - $ShadeB1) / 100) * $Value + $ShadeB1;
+                        $Alpha = (($ShadeA2 - $ShadeA1) / 100) * $Value + $ShadeA1;
+                    }
+
+                    $Settings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                    if ($Border) {
+                        $Settings["BorderR"] = $BorderR;
+                        $Settings["BorderG"] = $BorderG;
+                        $Settings["BorderB"] = $BorderB;
+                    }
+                    if ($Surrounding != -1) {
+                        $Settings["BorderR"] = $R + $Surrounding;
+                        $Settings["BorderG"] = $G + $Surrounding;
+                        $Settings["BorderB"] = $B + $Surrounding;
+                    }
+
+                    $this->pChartObject->drawFilledRectangle($X1, $Y1, $X2 - 1, $Y2 - 1, $Settings);
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute the missing points
+     */
+    public function computeMissing()
+    {
+        $Missing = [];
+        for ($X = 0; $X <= $this->GridSizeX; $X++) {
+            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
+                if ($this->Points[$X][$Y] == UNKNOWN) {
+                    $Missing[] = $X . "," . $Y;
+                }
+            }
+        }
+        shuffle($Missing);
+
+        foreach ($Missing as $Pos) {
+            $Pos = preg_split("/,/", $Pos);
+            $X = $Pos[0];
+            $Y = $Pos[1];
+
+            if ($this->Points[$X][$Y] == UNKNOWN) {
+                $NearestNeighbor = $this->getNearestNeighbor($X, $Y);
+
+                $Value = 0;
+                $Points = 0;
+                for ($Xi = $X - $NearestNeighbor; $Xi <= $X + $NearestNeighbor; $Xi++) {
+                    for ($Yi = $Y - $NearestNeighbor; $Yi <= $Y + $NearestNeighbor; $Yi++) {
+                        if ($Xi >= 0
+                            && $Yi >= 0
+                            && $Xi <= $this->GridSizeX
+                            && $Yi <= $this->GridSizeY
+                            && $this->Points[$Xi][$Yi] != UNKNOWN
+                            && $this->Points[$Xi][$Yi] != IGNORED
+                        ) {
+                            $Value = $Value + $this->Points[$Xi][$Yi];
+                            $Points++;
+                        }
+                    }
+                }
+
+                if ($Points != 0) {
+                    $this->Points[$X][$Y] = $Value / $Points;
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the nearest Neighbor distance of a point
+     * @param int $Xp
+     * @param int $Yp
+     * @return int
+     */
+    public function getNearestNeighbor($Xp, $Yp)
+    {
+        $Nearest = UNKNOWN;
+        for ($X = 0; $X <= $this->GridSizeX; $X++) {
+            for ($Y = 0; $Y <= $this->GridSizeY; $Y++) {
+                if ($this->Points[$X][$Y] != UNKNOWN && $this->Points[$X][$Y] != IGNORED) {
+                    $DistanceX = max($Xp, $X) - min($Xp, $X);
+                    $DistanceY = max($Yp, $Y) - min($Yp, $Y);
+                    $Distance = max($DistanceX, $DistanceY);
+                    if ($Distance < $Nearest || $Nearest == UNKNOWN) {
+                        $Nearest = $Distance;
+                    }
+                }
+            }
+        }
+        return $Nearest;
+    }
+}

+ 1318 - 0
vendor/szymach/c-pchart/src/Data.php

@@ -0,0 +1,1318 @@
+<?php
+
+namespace CpChart;
+
+use Exception;
+use RuntimeException;
+
+/**
+ *  Data - class to manipulate data arrays
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+class Data
+{
+    /**
+     * @var array
+     */
+    public $Data = [];
+
+    /**
+     * @var array
+     */
+    public $Palette = [
+        "0" => ["R" => 188, "G" => 224, "B" => 46, "Alpha" => 100],
+        "1" => ["R" => 224, "G" => 100, "B" => 46, "Alpha" => 100],
+        "2" => ["R" => 224, "G" => 214, "B" => 46, "Alpha" => 100],
+        "3" => ["R" => 46, "G" => 151, "B" => 224, "Alpha" => 100],
+        "4" => ["R" => 176, "G" => 46, "B" => 224, "Alpha" => 100],
+        "5" => ["R" => 224, "G" => 46, "B" => 117, "Alpha" => 100],
+        "6" => ["R" => 92, "G" => 224, "B" => 46, "Alpha" => 100],
+        "7" => ["R" => 224, "G" => 176, "B" => 46, "Alpha" => 100]
+    ];
+
+    public function __construct()
+    {
+        $this->Data["XAxisDisplay"] = AXIS_FORMAT_DEFAULT;
+        $this->Data["XAxisFormat"] = null;
+        $this->Data["XAxisName"] = null;
+        $this->Data["XAxisUnit"] = null;
+        $this->Data["Abscissa"] = null;
+        $this->Data["AbsicssaPosition"] = AXIS_POSITION_BOTTOM;
+
+        $this->Data["Axis"][0]["Display"] = AXIS_FORMAT_DEFAULT;
+        $this->Data["Axis"][0]["Position"] = AXIS_POSITION_LEFT;
+        $this->Data["Axis"][0]["Identity"] = AXIS_Y;
+    }
+
+    /**
+     * Add a single point or an array to the given serie
+     * @param mixed $Values
+     * @param string $SerieName
+     * @return int
+     */
+    public function addPoints($Values, $SerieName = "Serie1")
+    {
+        if (!isset($this->Data["Series"][$SerieName])) {
+            $this->initialise($SerieName);
+        }
+        if (is_array($Values)) {
+            foreach ($Values as $Value) {
+                $this->Data["Series"][$SerieName]["Data"][] = $Value;
+            }
+        } else {
+            $this->Data["Series"][$SerieName]["Data"][] = $Values;
+        }
+
+        if ($Values != VOID) {
+            $StrippedData = $this->stripVOID($this->Data["Series"][$SerieName]["Data"]);
+            if (empty($StrippedData)) {
+                $this->Data["Series"][$SerieName]["Max"] = 0;
+                $this->Data["Series"][$SerieName]["Min"] = 0;
+                return 0;
+            }
+            $this->Data["Series"][$SerieName]["Max"] = max($StrippedData);
+            $this->Data["Series"][$SerieName]["Min"] = min($StrippedData);
+        }
+    }
+
+    /**
+     * Strip VOID values
+     * @param mixed $Values
+     * @return array
+     */
+    public function stripVOID($Values)
+    {
+        if (!is_array($Values)) {
+            return [];
+        }
+        $Result = [];
+        foreach ($Values as $Value) {
+            if ($Value != VOID) {
+                $Result[] = $Value;
+            }
+        }
+        return $Result;
+    }
+
+    /**
+     * Return the number of values contained in a given serie
+     * @param string $Serie
+     * @return int
+     */
+    public function getSerieCount($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie]["Data"])) {
+            return sizeof($this->Data["Series"][$Serie]["Data"]);
+        }
+        return 0;
+    }
+
+    /**
+     * Remove a serie from the pData object
+     * @param mixed $Series
+     */
+    public function removeSerie($Series)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                unset($this->Data["Series"][$Serie]);
+            }
+        }
+    }
+
+    /**
+     * Return a value from given serie & index
+     * @param string $Serie
+     * @param int $Index
+     * @return mixed
+     */
+    public function getValueAt($Serie, $Index = 0)
+    {
+        if (isset($this->Data["Series"][$Serie]["Data"][$Index])) {
+            return $this->Data["Series"][$Serie]["Data"][$Index];
+        }
+        return null;
+    }
+
+    /**
+     * Return the values array
+     * @param string $Serie
+     * @return mixed
+     */
+    public function getValues($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie]["Data"])) {
+            return $this->Data["Series"][$Serie]["Data"];
+        }
+        return null;
+    }
+
+    /**
+     * Reverse the values in the given serie
+     * @param mixed $Series
+     */
+    public function reverseSerie($Series)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie]["Data"])) {
+                $this->Data["Series"][$Serie]["Data"] = array_reverse(
+                    $this->Data["Series"][$Serie]["Data"]
+                );
+            }
+        }
+    }
+
+    /**
+     * Return the sum of the serie values
+     * @param string $Serie
+     * @return int|null
+     */
+    public function getSum($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            return array_sum($this->Data["Series"][$Serie]["Data"]);
+        }
+        return null;
+    }
+
+    /**
+     * Return the max value of a given serie
+     * @param string $Serie
+     * @return mixed
+     */
+    public function getMax($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie]["Max"])) {
+            return $this->Data["Series"][$Serie]["Max"];
+        }
+        return null;
+    }
+
+    /**
+     * @param string $Serie
+     * @return mixed
+     */
+    public function getMin($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie]["Min"])) {
+            return $this->Data["Series"][$Serie]["Min"];
+        }
+        return null;
+    }
+
+    /**
+     * Set the description of a given serie
+     * @param mixed $Series
+     * @param string $Shape
+     */
+    public function setSerieShape($Series, $Shape = SERIE_SHAPE_FILLEDCIRCLE)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["Shape"] = $Shape;
+            }
+        }
+    }
+
+    /**
+     * Set the description of a given serie
+     * @param string|array $Series
+     * @param string $Description
+     */
+    public function setSerieDescription($Series, $Description = "My serie")
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["Description"] = $Description;
+            }
+        }
+    }
+
+    /**
+     * Set a serie as "drawable" while calling a rendering public function
+     * @param string|array $Series
+     * @param boolean $Drawable
+     */
+    public function setSerieDrawable($Series, $Drawable = true)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["isDrawable"] = $Drawable;
+            }
+        }
+    }
+
+    /**
+     * Set the icon associated to a given serie
+     * @param mixed $Series
+     * @param mixed $Picture
+     */
+    public function setSeriePicture($Series, $Picture = null)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["Picture"] = $Picture;
+            }
+        }
+    }
+
+    /**
+     * Set the name of the X Axis
+     * @param string $Name
+     */
+    public function setXAxisName($Name)
+    {
+        $this->Data["XAxisName"] = $Name;
+    }
+
+    /**
+     * Set the display mode of the  X Axis
+     * @param int $Mode
+     * @param array $Format
+     */
+    public function setXAxisDisplay($Mode, $Format = null)
+    {
+        $this->Data["XAxisDisplay"] = $Mode;
+        $this->Data["XAxisFormat"] = $Format;
+    }
+
+    /**
+     * Set the unit that will be displayed on the X axis
+     * @param string $Unit
+     */
+    public function setXAxisUnit($Unit)
+    {
+        $this->Data["XAxisUnit"] = $Unit;
+    }
+
+    /**
+     * Set the serie that will be used as abscissa
+     * @param string $Serie
+     */
+    public function setAbscissa($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $this->Data["Abscissa"] = $Serie;
+        }
+    }
+
+    /**
+     * Set the position of the abscissa axis
+     * @param int $Position
+     */
+    public function setAbsicssaPosition($Position = AXIS_POSITION_BOTTOM)
+    {
+        $this->Data["AbsicssaPosition"] = $Position;
+    }
+
+    /**
+     * Set the name of the abscissa axis
+     * @param string $Name
+     */
+    public function setAbscissaName($Name)
+    {
+        $this->Data["AbscissaName"] = $Name;
+    }
+
+    /**
+     * Create a scatter group specified in X and Y data series
+     * @param string $SerieX
+     * @param string $SerieY
+     * @param int $ID
+     */
+    public function setScatterSerie($SerieX, $SerieY, $ID = 0)
+    {
+        if (isset($this->Data["Series"][$SerieX]) && isset($this->Data["Series"][$SerieY])) {
+            $this->initScatterSerie($ID);
+            $this->Data["ScatterSeries"][$ID]["X"] = $SerieX;
+            $this->Data["ScatterSeries"][$ID]["Y"] = $SerieY;
+        }
+    }
+
+    /**
+     *  Set the shape of a given sctatter serie
+     * @param int $ID
+     * @param int $Shape
+     */
+    public function setScatterSerieShape($ID, $Shape = SERIE_SHAPE_FILLEDCIRCLE)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Shape"] = $Shape;
+        }
+    }
+
+    /**
+     * Set the description of a given scatter serie
+     * @param int $ID
+     * @param string $Description
+     */
+    public function setScatterSerieDescription($ID, $Description = "My serie")
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Description"] = $Description;
+        }
+    }
+
+    /**
+     * Set the icon associated to a given scatter serie
+     * @param int $ID
+     * @param mixed $Picture
+     */
+    public function setScatterSeriePicture($ID, $Picture = null)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Picture"] = $Picture;
+        }
+    }
+
+    /**
+     * Set a scatter serie as "drawable" while calling a rendering public function
+     * @param int $ID
+     * @param boolean $Drawable
+     */
+    public function setScatterSerieDrawable($ID, $Drawable = true)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["isDrawable"] = $Drawable;
+        }
+    }
+
+    /**
+     * Define if a scatter serie should be draw with ticks
+     * @param int $ID
+     * @param int $Width
+     */
+    public function setScatterSerieTicks($ID, $Width = 0)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Ticks"] = $Width;
+        }
+    }
+
+    /**
+     * Define if a scatter serie should be draw with a special weight
+     * @param int $ID
+     * @param int $Weight
+     */
+    public function setScatterSerieWeight($ID, $Weight = 0)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Weight"] = $Weight;
+        }
+    }
+
+    /**
+     * Associate a color to a scatter serie
+     * @param int $ID
+     * @param array $Format
+     */
+    public function setScatterSerieColor($ID, array $Format)
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Color"]["R"] = $R;
+            $this->Data["ScatterSeries"][$ID]["Color"]["G"] = $G;
+            $this->Data["ScatterSeries"][$ID]["Color"]["B"] = $B;
+            $this->Data["ScatterSeries"][$ID]["Color"]["Alpha"] = $Alpha;
+        }
+    }
+
+    /**
+     * Compute the series limits for an individual and global point of view
+     * @return array
+     */
+    public function limits()
+    {
+        $GlobalMin = ABSOLUTE_MAX;
+        $GlobalMax = ABSOLUTE_MIN;
+
+        foreach (array_keys($this->Data["Series"]) as $Key) {
+            if ($this->Data["Abscissa"] != $Key
+                && $this->Data["Series"][$Key]["isDrawable"] == true
+            ) {
+                if ($GlobalMin > $this->Data["Series"][$Key]["Min"]) {
+                    $GlobalMin = $this->Data["Series"][$Key]["Min"];
+                }
+                if ($GlobalMax < $this->Data["Series"][$Key]["Max"]) {
+                    $GlobalMax = $this->Data["Series"][$Key]["Max"];
+                }
+            }
+        }
+        $this->Data["Min"] = $GlobalMin;
+        $this->Data["Max"] = $GlobalMax;
+
+        return [$GlobalMin, $GlobalMax];
+    }
+
+    /**
+     * Mark all series as drawable
+     */
+    public function drawAll()
+    {
+        foreach (array_keys($this->Data["Series"]) as $Key) {
+            if ($this->Data["Abscissa"] != $Key) {
+                $this->Data["Series"][$Key]["isDrawable"] = true;
+            }
+        }
+    }
+
+    /**
+     * Return the average value of the given serie
+     * @param string $Serie
+     * @return int|null
+     */
+    public function getSerieAverage($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
+            return array_sum($SerieData) / sizeof($SerieData);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the geometric mean of the given serie
+     * @param string $Serie
+     * @return int|null
+     */
+    public function getGeometricMean($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
+            $Seriesum = 1;
+            foreach ($SerieData as $Value) {
+                $Seriesum = $Seriesum * $Value;
+            }
+            return pow($Seriesum, 1 / sizeof($SerieData));
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the harmonic mean of the given serie
+     * @param string $Serie
+     * @return int|null
+     */
+    public function getHarmonicMean($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
+            $Seriesum = 0;
+            foreach ($SerieData as $Value) {
+                $Seriesum = $Seriesum + 1 / $Value;
+            }
+            return sizeof($SerieData) / $Seriesum;
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the standard deviation of the given serie
+     * @param string $Serie
+     * @return double|null
+     */
+    public function getStandardDeviation($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $Average = $this->getSerieAverage($Serie);
+            $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
+
+            $DeviationSum = 0;
+            foreach ($SerieData as $Key => $Value) {
+                $DeviationSum = $DeviationSum + ($Value - $Average) * ($Value - $Average);
+            }
+            return sqrt($DeviationSum / count($SerieData));
+        }
+        return null;
+    }
+
+    /**
+     * Return the Coefficient of variation of the given serie
+     * @param string $Serie
+     * @return float|null
+     */
+    public function getCoefficientOfVariation($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $Average = $this->getSerieAverage($Serie);
+            $StandardDeviation = $this->getStandardDeviation($Serie);
+
+            if ($StandardDeviation != 0) {
+                return $StandardDeviation / $Average;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the median value of the given serie
+     * @param string $Serie
+     * @return int|float
+     */
+    public function getSerieMedian($Serie)
+    {
+        if (isset($this->Data["Series"][$Serie])) {
+            $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
+            sort($SerieData);
+            $SerieCenter = floor(sizeof($SerieData) / 2);
+
+            if (isset($SerieData[$SerieCenter])) {
+                return $SerieData[$SerieCenter];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the x th percentil of the given serie
+     * @param string $Serie
+     * @param int $Percentil
+     * @return int|float| null
+     */
+    public function getSeriePercentile($Serie = "Serie1", $Percentil = 95)
+    {
+        if (!isset($this->Data["Series"][$Serie]["Data"])) {
+            return null;
+        }
+
+        $Values = count($this->Data["Series"][$Serie]["Data"]) - 1;
+        if ($Values < 0) {
+            $Values = 0;
+        }
+
+        $PercentilID = floor(($Values / 100) * $Percentil + .5);
+        $SortedValues = $this->Data["Series"][$Serie]["Data"];
+        sort($SortedValues);
+
+        if (is_numeric($SortedValues[$PercentilID])) {
+            return $SortedValues[$PercentilID];
+        }
+        return null;
+    }
+
+    /**
+     * Add random values to a given serie
+     * @param string $SerieName
+     * @param array $Options
+     */
+    public function addRandomValues($SerieName = "Serie1", array $Options = [])
+    {
+        $Values = isset($Options["Values"]) ? $Options["Values"] : 20;
+        $Min = isset($Options["Min"]) ? $Options["Min"] : 0;
+        $Max = isset($Options["Max"]) ? $Options["Max"] : 100;
+        $withFloat = isset($Options["withFloat"]) ? $Options["withFloat"] : false;
+
+        for ($i = 0; $i <= $Values; $i++) {
+            $Value = $withFloat ? rand($Min * 100, $Max * 100) / 100 : rand($Min, $Max);
+            $this->addPoints($Value, $SerieName);
+        }
+    }
+
+    /**
+     * Test if we have valid data
+     * @return boolean|null
+     */
+    public function containsData()
+    {
+        if (!isset($this->Data["Series"])) {
+            return false;
+        }
+
+        foreach (array_keys($this->Data["Series"]) as $Key) {
+            if ($this->Data["Abscissa"] != $Key
+                && $this->Data["Series"][$Key]["isDrawable"] == true
+            ) {
+                return true;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Set the display mode of an Axis
+     * @param int $AxisID
+     * @param int $Mode
+     * @param array $Format
+     */
+    public function setAxisDisplay($AxisID, $Mode = AXIS_FORMAT_DEFAULT, $Format = null)
+    {
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Display"] = $Mode;
+            if ($Format != null) {
+                $this->Data["Axis"][$AxisID]["Format"] = $Format;
+            }
+        }
+    }
+
+    /**
+     * Set the position of an Axis
+     * @param int $AxisID
+     * @param int $Position
+     */
+    public function setAxisPosition($AxisID, $Position = AXIS_POSITION_LEFT)
+    {
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Position"] = $Position;
+        }
+    }
+
+    /**
+     * Associate an unit to an axis
+     * @param int $AxisID
+     * @param string $Unit
+     */
+    public function setAxisUnit($AxisID, $Unit)
+    {
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Unit"] = $Unit;
+        }
+    }
+
+    /**
+     * Associate a name to an axis
+     * @param int $AxisID
+     * @param string $Name
+     */
+    public function setAxisName($AxisID, $Name)
+    {
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Name"] = $Name;
+        }
+    }
+
+    /**
+     * Associate a color to an axis
+     * @param int $AxisID
+     * @param array $Format
+     */
+    public function setAxisColor($AxisID, array $Format)
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Color"]["R"] = $R;
+            $this->Data["Axis"][$AxisID]["Color"]["G"] = $G;
+            $this->Data["Axis"][$AxisID]["Color"]["B"] = $B;
+            $this->Data["Axis"][$AxisID]["Color"]["Alpha"] = $Alpha;
+        }
+    }
+
+    /**
+     * Design an axis as X or Y member
+     * @param int $AxisID
+     * @param int $Identity
+     */
+    public function setAxisXY($AxisID, $Identity = AXIS_Y)
+    {
+        if (isset($this->Data["Axis"][$AxisID])) {
+            $this->Data["Axis"][$AxisID]["Identity"] = $Identity;
+        }
+    }
+
+    /**
+     * Associate one data serie with one axis
+     * @param mixed $Series
+     * @param int $AxisID
+     */
+    public function setSerieOnAxis($Series, $AxisID)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            $PreviousAxis = $this->Data["Series"][$Serie]["Axis"];
+
+            /* Create missing axis */
+            if (!isset($this->Data["Axis"][$AxisID])) {
+                $this->Data["Axis"][$AxisID]["Position"] = AXIS_POSITION_LEFT;
+                $this->Data["Axis"][$AxisID]["Identity"] = AXIS_Y;
+            }
+
+            $this->Data["Series"][$Serie]["Axis"] = $AxisID;
+
+            /* Cleanup unused axis */
+            $Found = false;
+            foreach ($this->Data["Series"] as $Values) {
+                if ($Values["Axis"] == $PreviousAxis) {
+                    $Found = true;
+                }
+            }
+            if (!$Found) {
+                unset($this->Data["Axis"][$PreviousAxis]);
+            }
+        }
+    }
+
+    /**
+     * Define if a serie should be draw with ticks
+     * @param mixed $Series
+     * @param int $Width
+     */
+    public function setSerieTicks($Series, $Width = 0)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["Ticks"] = $Width;
+            }
+        }
+    }
+
+    /**
+     * Define if a serie should be draw with a special weight
+     * @param mixed $Series
+     * @param int $Weight
+     */
+    public function setSerieWeight($Series, $Weight = 0)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Serie) {
+            if (isset($this->Data["Series"][$Serie])) {
+                $this->Data["Series"][$Serie]["Weight"] = $Weight;
+            }
+        }
+    }
+
+    /**
+     * Returns the palette of the given serie
+     * @param type $Serie
+     * @return null
+     */
+    public function getSeriePalette($Serie)
+    {
+        if (!isset($this->Data["Series"][$Serie])) {
+            return null;
+        }
+
+        $Result = [];
+        $Result["R"] = $this->Data["Series"][$Serie]["Color"]["R"];
+        $Result["G"] = $this->Data["Series"][$Serie]["Color"]["G"];
+        $Result["B"] = $this->Data["Series"][$Serie]["Color"]["B"];
+        $Result["Alpha"] = $this->Data["Series"][$Serie]["Color"]["Alpha"];
+
+        return $Result;
+    }
+
+    /**
+     * Set the color of one serie
+     * @param mixed $Series
+     * @param array $Format
+     */
+    public function setPalette($Series, array $Format = [])
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+
+        foreach ($Series as $Key => $Serie) {
+            $R = isset($Format["R"]) ? $Format["R"] : 0;
+            $G = isset($Format["G"]) ? $Format["G"] : 0;
+            $B = isset($Format["B"]) ? $Format["B"] : 0;
+            $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+            if (isset($this->Data["Series"][$Serie])) {
+                $OldR = $this->Data["Series"][$Serie]["Color"]["R"];
+                $OldG = $this->Data["Series"][$Serie]["Color"]["G"];
+                $OldB = $this->Data["Series"][$Serie]["Color"]["B"];
+                $this->Data["Series"][$Serie]["Color"]["R"] = $R;
+                $this->Data["Series"][$Serie]["Color"]["G"] = $G;
+                $this->Data["Series"][$Serie]["Color"]["B"] = $B;
+                $this->Data["Series"][$Serie]["Color"]["Alpha"] = $Alpha;
+
+                /* Do reverse processing on the internal palette array */
+                foreach ($this->Palette as $Key => $Value) {
+                    if ($Value["R"] == $OldR && $Value["G"] == $OldG && $Value["B"] == $OldB) {
+                        $this->Palette[$Key]["R"] = $R;
+                        $this->Palette[$Key]["G"] = $G;
+                        $this->Palette[$Key]["B"] = $B;
+                        $this->Palette[$Key]["Alpha"] = $Alpha;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Load a palette file
+     * @param string $FileName
+     * @param boolean $Overwrite
+     * @throws Exception
+     */
+    public function loadPalette($FileName, $Overwrite = false)
+    {
+        $path = file_exists($FileName)
+            ? $FileName
+            : sprintf('%s/../resources/palettes/%s', __DIR__, ltrim($FileName, '/'))
+        ;
+
+        $fileHandle = @fopen($path, "r");
+        if (!$fileHandle) {
+            throw new Exception(sprintf(
+                'The requested palette "%s" was not found at path "%s"!',
+                $FileName,
+                $path
+            ));
+        }
+
+        if ($Overwrite) {
+            $this->Palette = [];
+        }
+
+        while (!feof($fileHandle)) {
+            $line = fgets($fileHandle, 4096);
+            if (false === $line) {
+                continue;
+            }
+            $row = explode(',', $line);
+            if (empty($row)) {
+                continue;
+            }
+            if (count($row) !== 4) {
+                throw new RuntimeException(sprintf(
+                    'A palette row must supply R, G, B and Alpha components, %s given!',
+                    var_export($row, true)
+                ));
+            }
+            list($R, $G, $B, $Alpha) = $row;
+            $ID = count($this->Palette);
+            $this->Palette[$ID] = [
+                "R" => trim($R),
+                "G" => trim($G),
+                "B" => trim($B),
+                "Alpha" => trim($Alpha)
+            ];
+        }
+        fclose($fileHandle);
+
+        /* Apply changes to current series */
+        $ID = 0;
+        if (isset($this->Data["Series"])) {
+            foreach ($this->Data["Series"] as $Key => $Value) {
+                if (!isset($this->Palette[$ID])) {
+                    $this->Data["Series"][$Key]["Color"] = ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 0];
+                } else {
+                    $this->Data["Series"][$Key]["Color"] = $this->Palette[$ID];
+                }
+                $ID++;
+            }
+        }
+    }
+
+    /**
+     * Initialise a given scatter serie
+     * @param int $ID
+     * @return null
+     */
+    public function initScatterSerie($ID)
+    {
+        if (isset($this->Data["ScatterSeries"][$ID])) {
+            return null;
+        }
+
+        $this->Data["ScatterSeries"][$ID]["Description"] = "Scatter " . $ID;
+        $this->Data["ScatterSeries"][$ID]["isDrawable"] = true;
+        $this->Data["ScatterSeries"][$ID]["Picture"] = null;
+        $this->Data["ScatterSeries"][$ID]["Ticks"] = 0;
+        $this->Data["ScatterSeries"][$ID]["Weight"] = 0;
+
+        if (isset($this->Palette[$ID])) {
+            $this->Data["ScatterSeries"][$ID]["Color"] = $this->Palette[$ID];
+        } else {
+            $this->Data["ScatterSeries"][$ID]["Color"]["R"] = rand(0, 255);
+            $this->Data["ScatterSeries"][$ID]["Color"]["G"] = rand(0, 255);
+            $this->Data["ScatterSeries"][$ID]["Color"]["B"] = rand(0, 255);
+            $this->Data["ScatterSeries"][$ID]["Color"]["Alpha"] = 100;
+        }
+    }
+
+    /**
+     * Initialise a given serie
+     * @param string $Serie
+     */
+    public function initialise($Serie)
+    {
+        $ID = 0;
+        if (isset($this->Data["Series"])) {
+            $ID = count($this->Data["Series"]);
+        }
+
+        $this->Data["Series"][$Serie]["Description"] = $Serie;
+        $this->Data["Series"][$Serie]["isDrawable"] = true;
+        $this->Data["Series"][$Serie]["Picture"] = null;
+        $this->Data["Series"][$Serie]["Max"] = null;
+        $this->Data["Series"][$Serie]["Min"] = null;
+        $this->Data["Series"][$Serie]["Axis"] = 0;
+        $this->Data["Series"][$Serie]["Ticks"] = 0;
+        $this->Data["Series"][$Serie]["Weight"] = 0;
+        $this->Data["Series"][$Serie]["Shape"] = SERIE_SHAPE_FILLEDCIRCLE;
+
+        if (isset($this->Palette[$ID])) {
+            $this->Data["Series"][$Serie]["Color"] = $this->Palette[$ID];
+        } else {
+            $this->Data["Series"][$Serie]["Color"]["R"] = rand(0, 255);
+            $this->Data["Series"][$Serie]["Color"]["G"] = rand(0, 255);
+            $this->Data["Series"][$Serie]["Color"]["B"] = rand(0, 255);
+            $this->Data["Series"][$Serie]["Color"]["Alpha"] = 100;
+        }
+        $this->Data["Series"][$Serie]["Data"] = [];
+    }
+
+    /**
+     * @param int $NormalizationFactor
+     * @param mixed $UnitChange
+     * @param int $Round
+     */
+    public function normalize($NormalizationFactor = 100, $UnitChange = null, $Round = 1)
+    {
+        $Abscissa = $this->Data["Abscissa"];
+
+        $SelectedSeries = [];
+        $MaxVal = 0;
+        foreach (array_keys($this->Data["Axis"]) as $AxisID) {
+            if ($UnitChange != null) {
+                $this->Data["Axis"][$AxisID]["Unit"] = $UnitChange;
+            }
+
+            foreach ($this->Data["Series"] as $SerieName => $Serie) {
+                if ($Serie["Axis"] == $AxisID
+                    && $Serie["isDrawable"] == true
+                    && $SerieName != $Abscissa
+                ) {
+                    $SelectedSeries[$SerieName] = $SerieName;
+
+                    if (count($Serie["Data"]) > $MaxVal) {
+                        $MaxVal = count($Serie["Data"]);
+                    }
+                }
+            }
+        }
+
+        for ($i = 0; $i <= $MaxVal - 1; $i++) {
+            $Factor = 0;
+            foreach ($SelectedSeries as $Key => $SerieName) {
+                $Value = $this->Data["Series"][$SerieName]["Data"][$i];
+                if ($Value != VOID) {
+                    $Factor = $Factor + abs($Value);
+                }
+            }
+
+            if ($Factor != 0) {
+                $Factor = $NormalizationFactor / $Factor;
+
+                foreach ($SelectedSeries as $Key => $SerieName) {
+                    $Value = $this->Data["Series"][$SerieName]["Data"][$i];
+
+                    if ($Value != VOID && $Factor != $NormalizationFactor) {
+                        $this->Data["Series"][$SerieName]["Data"][$i] = round(abs($Value) * $Factor, $Round);
+                    } elseif ($Value == VOID || $Value == 0) {
+                        $this->Data["Series"][$SerieName]["Data"][$i] = VOID;
+                    } elseif ($Factor == $NormalizationFactor) {
+                        $this->Data["Series"][$SerieName]["Data"][$i] = $NormalizationFactor;
+                    }
+                }
+            }
+        }
+
+        foreach ($SelectedSeries as $Key => $SerieName) {
+            $this->Data["Series"][$SerieName]["Max"] = max(
+                $this->stripVOID($this->Data["Series"][$SerieName]["Data"])
+            );
+            $this->Data["Series"][$SerieName]["Min"] = min(
+                $this->stripVOID($this->Data["Series"][$SerieName]["Data"])
+            );
+        }
+    }
+
+    /**
+     * Load data from a CSV (or similar) data source
+     * @param string $FileName
+     * @param array $Options
+     */
+    public function importFromCSV($FileName, array $Options = [])
+    {
+        $Delimiter = isset($Options["Delimiter"]) ? $Options["Delimiter"] : ",";
+        $GotHeader = isset($Options["GotHeader"]) ? $Options["GotHeader"] : false;
+        $SkipColumns = isset($Options["SkipColumns"]) ? $Options["SkipColumns"] : [-1];
+        $DefaultSerieName = isset($Options["DefaultSerieName"]) ? $Options["DefaultSerieName"] : "Serie";
+
+        $Handle = @fopen($FileName, "r");
+        if ($Handle) {
+            $HeaderParsed = false;
+            $SerieNames = [];
+            while (!feof($Handle)) {
+                $Buffer = fgets($Handle, 4096);
+                $Buffer = str_replace(chr(10), "", $Buffer);
+                $Buffer = str_replace(chr(13), "", $Buffer);
+                $Values = preg_split("/" . $Delimiter . "/", $Buffer);
+
+                if ($Buffer != "") {
+                    if ($GotHeader && !$HeaderParsed) {
+                        foreach ($Values as $Key => $Name) {
+                            if (!in_array($Key, $SkipColumns)) {
+                                $SerieNames[$Key] = $Name;
+                            }
+                        }
+                        $HeaderParsed = true;
+                    } else {
+                        if (!count($SerieNames)) {
+                            foreach ($Values as $Key => $Name) {
+                                if (!in_array($Key, $SkipColumns)) {
+                                    $SerieNames[$Key] = $DefaultSerieName . $Key;
+                                }
+                            }
+                        }
+                        foreach ($Values as $Key => $Value) {
+                            if (!in_array($Key, $SkipColumns)) {
+                                $this->addPoints($Value, $SerieNames[$Key]);
+                            }
+                        }
+                    }
+                }
+            }
+            fclose($Handle);
+        }
+    }
+
+    /**
+     * Create a dataset based on a formula
+     *
+     * @param string $SerieName
+     * @param string $Formula
+     * @param array $Options
+     * @return null
+     */
+    public function createFunctionSerie($SerieName, $Formula = "", array $Options = [])
+    {
+        $MinX = isset($Options["MinX"]) ? $Options["MinX"] : -10;
+        $MaxX = isset($Options["MaxX"]) ? $Options["MaxX"] : 10;
+        $XStep = isset($Options["XStep"]) ? $Options["XStep"] : 1;
+        $AutoDescription = isset($Options["AutoDescription"]) ? $Options["AutoDescription"] : false;
+        $RecordAbscissa = isset($Options["RecordAbscissa"]) ? $Options["RecordAbscissa"] : false;
+        $AbscissaSerie = isset($Options["AbscissaSerie"]) ? $Options["AbscissaSerie"] : "Abscissa";
+
+        if ($Formula == "") {
+            return null;
+        }
+
+        $Result = [];
+        $Abscissa = [];
+        for ($i = $MinX; $i <= $MaxX; $i = $i + $XStep) {
+            $Expression = "\$return = '!'.(" . str_replace("z", $i, $Formula) . ");";
+            if (@eval($Expression) === false) {
+                $return = VOID;
+            }
+            if ($return == "!") {
+                $return = VOID;
+            } else {
+                $return = $this->right($return, strlen($return) - 1);
+            }
+            if ($return == "NAN") {
+                $return = VOID;
+            }
+            if ($return == "INF") {
+                $return = VOID;
+            }
+            if ($return == "-INF") {
+                $return = VOID;
+            }
+
+            $Abscissa[] = $i;
+            $Result[] = $return;
+        }
+
+        $this->addPoints($Result, $SerieName);
+        if ($AutoDescription) {
+            $this->setSerieDescription($SerieName, $Formula);
+        }
+        if ($RecordAbscissa) {
+            $this->addPoints($Abscissa, $AbscissaSerie);
+        }
+    }
+
+    /**
+     * @param mixed $Series
+     */
+    public function negateValues($Series)
+    {
+        if (!is_array($Series)) {
+            $Series = $this->convertToArray($Series);
+        }
+        foreach ($Series as $Key => $SerieName) {
+            if (isset($this->Data["Series"][$SerieName])) {
+                $Data = [];
+                foreach ($this->Data["Series"][$SerieName]["Data"] as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Data[] = VOID;
+                    } else {
+                        $Data[] = -$Value;
+                    }
+                }
+                $this->Data["Series"][$SerieName]["Data"] = $Data;
+                $this->Data["Series"][$SerieName]["Max"] = max(
+                    $this->stripVOID($this->Data["Series"][$SerieName]["Data"])
+                );
+                $this->Data["Series"][$SerieName]["Min"] = min(
+                    $this->stripVOID($this->Data["Series"][$SerieName]["Data"])
+                );
+            }
+        }
+    }
+
+    /**
+     * Return the data & configuration of the series
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->Data;
+    }
+
+    /**
+     * Save a palette element
+     *
+     * @param integer $ID
+     * @param string $Color
+     */
+    public function savePalette($ID, $Color)
+    {
+        $this->Palette[$ID] = $Color;
+    }
+
+    /**
+     * Return the palette of the series
+     * @return array
+     */
+    public function getPalette()
+    {
+        return $this->Palette;
+    }
+
+    /**
+     * Called by the scaling algorithm to save the config
+     * @param mixed $Axis
+     */
+    public function saveAxisConfig($Axis)
+    {
+        $this->Data["Axis"] = $Axis;
+    }
+
+    /**
+     * Save the Y Margin if set
+     * @param mixed $Value
+     */
+    public function saveYMargin($Value)
+    {
+        $this->Data["YMargin"] = $Value;
+    }
+
+    /**
+     * Save extended configuration to the pData object
+     * @param string $Tag
+     * @param mixed $Values
+     */
+    public function saveExtendedData($Tag, $Values)
+    {
+        $this->Data["Extended"][$Tag] = $Values;
+    }
+
+    /**
+     * Called by the scaling algorithm to save the orientation of the scale
+     * @param mixed $Orientation
+     */
+    public function saveOrientation($Orientation)
+    {
+        $this->Data["Orientation"] = $Orientation;
+    }
+
+    /**
+     * Convert a string to a single elements array
+     * @param mixed $Value
+     * @return array
+     */
+    public function convertToArray($Value)
+    {
+        return [$Value];
+    }
+
+    /**
+     * Class string wrapper
+     * @return string
+     */
+    public function __toString()
+    {
+        return "pData object.";
+    }
+
+    /**
+     * @param string $value
+     * @param int $NbChar
+     * @return string
+     */
+    public function left($value, $NbChar)
+    {
+        return substr($value, 0, $NbChar);
+    }
+
+    /**
+     * @param string $value
+     * @param int $NbChar
+     * @return string
+     */
+    public function right($value, $NbChar)
+    {
+        return substr($value, strlen($value) - $NbChar, $NbChar);
+    }
+
+    /**
+     * @param string $value
+     * @param int $Depart
+     * @param int $NbChar
+     * @return string
+     */
+    public function mid($value, $Depart, $NbChar)
+    {
+        return substr($value, $Depart - 1, $NbChar);
+    }
+}

+ 10363 - 0
vendor/szymach/c-pchart/src/Draw.php

@@ -0,0 +1,10363 @@
+<?php
+
+namespace CpChart;
+
+use Exception;
+
+/**
+ *  Draw - class extension with drawing methods
+ *
+ *  Version     : 2.1.4
+ *  Made by     : Jean-Damien POGOLOTTI
+ *  Last Update : 19/01/2014
+ *
+ *  This file can be distributed under the license you can find at :
+ *
+ *  http://www.pchart.net/license
+ *
+ *  You can find the whole class documentation on the pChart web site.
+ */
+abstract class Draw extends BaseDraw
+{
+    /**
+     * Draw a polygon
+     * @param array $Points
+     * @param array $Format
+     */
+    public function drawPolygon(array $Points, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $NoFill = isset($Format["NoFill"]) ? $Format["NoFill"] : false;
+        $NoBorder = isset($Format["NoBorder"]) ? $Format["NoBorder"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BorderAlpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $Alpha / 2;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $SkipX = isset($Format["SkipX"]) ? $Format["SkipX"] : OUT_OF_SIGHT;
+        $SkipY = isset($Format["SkipY"]) ? $Format["SkipY"] : OUT_OF_SIGHT;
+
+        /* Calling the ImageFilledPolygon() public function over the $Points array will round it */
+        $Backup = $Points;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        if ($SkipX != OUT_OF_SIGHT) {
+            $SkipX = floor($SkipX);
+        }
+        if ($SkipY != OUT_OF_SIGHT) {
+            $SkipY = floor($SkipY);
+        }
+
+        $RestoreShadow = $this->Shadow;
+        if (!$NoFill) {
+            if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+                $this->Shadow = false;
+                for ($i = 0; $i <= count($Points) - 1; $i = $i + 2) {
+                    $Shadow[] = $Points[$i] + $this->ShadowX;
+                    $Shadow[] = $Points[$i + 1] + $this->ShadowY;
+                }
+                $this->drawPolygon(
+                    $Shadow,
+                    [
+                        "R" => $this->ShadowR,
+                        "G" => $this->ShadowG,
+                        "B" => $this->ShadowB,
+                        "Alpha" => $this->Shadowa,
+                        "NoBorder" => true
+                    ]
+                );
+            }
+
+            $FillColor = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+
+            if (count($Points) >= 6) {
+                $this->imageFilledPolygonWrapper($this->Picture, $Points, count($Points) / 2, $FillColor);
+            }
+        }
+
+        if (!$NoBorder) {
+            $Points = $Backup;
+
+            if ($NoFill) {
+                $BorderSettings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+            } else {
+                $BorderSettings = [
+                    "R" => $BorderR,
+                    "G" => $BorderG,
+                    "B" => $BorderB,
+                    "Alpha" => $BorderAlpha
+                ];
+            }
+
+            for ($i = 0; $i <= count($Points) - 1; $i = $i + 2) {
+                if (isset($Points[$i + 2])
+                    && !($Points[$i] == $Points[$i + 2] && $Points[$i] == $SkipX)
+                    && !($Points[$i + 1] == $Points[$i + 3] && $Points[$i + 1] == $SkipY)
+                ) {
+                    $this->drawLine(
+                        $Points[$i],
+                        $Points[$i + 1],
+                        $Points[$i + 2],
+                        $Points[$i + 3],
+                        $BorderSettings
+                    );
+                } elseif (!($Points[$i] == $Points[0] && $Points[$i] == $SkipX)
+                    && !($Points[$i + 1] == $Points[1] && $Points[$i + 1] == $SkipY)
+                ) {
+                    $this->drawLine($Points[$i], $Points[$i + 1], $Points[0], $Points[1], $BorderSettings);
+                }
+            }
+        }
+
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a rectangle with rounded corners
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param int|float $Radius
+     * @param array $Format
+     * @return null|integer
+     */
+    public function drawRoundedRectangle($X1, $Y1, $X2, $Y2, $Radius, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+        list($X1, $Y1, $X2, $Y2) = $this->fixBoxCoordinates($X1, $Y1, $X2, $Y2);
+
+        if ($X2 - $X1 < $Radius) {
+            $Radius = floor((($X2 - $X1)) / 2);
+        }
+        if ($Y2 - $Y1 < $Radius) {
+            $Radius = floor((($Y2 - $Y1)) / 2);
+        }
+
+        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "NoBorder" => true];
+
+        if ($Radius <= 0) {
+            $this->drawRectangle($X1, $Y1, $X2, $Y2, $Color);
+            return 0;
+        }
+
+        if ($this->Antialias) {
+            $this->drawLine($X1 + $Radius, $Y1, $X2 - $Radius, $Y1, $Color);
+            $this->drawLine($X2, $Y1 + $Radius, $X2, $Y2 - $Radius, $Color);
+            $this->drawLine($X2 - $Radius, $Y2, $X1 + $Radius, $Y2, $Color);
+            $this->drawLine($X1, $Y1 + $Radius, $X1, $Y2 - $Radius, $Color);
+        } else {
+            $Color = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+            imageline($this->Picture, $X1 + $Radius, $Y1, $X2 - $Radius, $Y1, $Color);
+            imageline($this->Picture, $X2, $Y1 + $Radius, $X2, $Y2 - $Radius, $Color);
+            imageline($this->Picture, $X2 - $Radius, $Y2, $X1 + $Radius, $Y2, $Color);
+            imageline($this->Picture, $X1, $Y1 + $Radius, $X1, $Y2 - $Radius, $Color);
+        }
+
+        $Step = 360 / (2 * PI * $Radius);
+        for ($i = 0; $i <= 90; $i = $i + $Step) {
+            $X = cos(($i + 180) * PI / 180) * $Radius + $X1 + $Radius;
+            $Y = sin(($i + 180) * PI / 180) * $Radius + $Y1 + $Radius;
+            $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+
+            $X = cos(($i + 90) * PI / 180) * $Radius + $X1 + $Radius;
+            $Y = sin(($i + 90) * PI / 180) * $Radius + $Y2 - $Radius;
+            $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+
+            $X = cos($i * PI / 180) * $Radius + $X2 - $Radius;
+            $Y = sin($i * PI / 180) * $Radius + $Y2 - $Radius;
+            $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+
+            $X = cos(($i + 270) * PI / 180) * $Radius + $X2 - $Radius;
+            $Y = sin(($i + 270) * PI / 180) * $Radius + $Y1 + $Radius;
+            $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+        }
+    }
+
+    /**
+     * Draw a rectangle with rounded corners
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param int|float $Radius
+     * @param array $Format
+     * @return null|integer
+     */
+    public function drawRoundedFilledRectangle($X1, $Y1, $X2, $Y2, $Radius, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : -1;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : -1;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : -1;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+
+        /* Temporary fix for AA issue */
+        $Y1 = (int) floor($Y1);
+        $Y2 = (int) floor($Y2);
+        $X1 = (int) floor($X1);
+        $X2 = (int) floor($X2);
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+        if ($BorderR == -1) {
+            $BorderR = $R;
+            $BorderG = $G;
+            $BorderB = $B;
+        }
+
+        list($X1, $Y1, $X2, $Y2) = $this->fixBoxCoordinates($X1, $Y1, $X2, $Y2);
+
+        if ($X2 - $X1 < $Radius * 2) {
+            $Radius = (int) floor((($X2 - $X1)) / 4);
+        }
+        if ($Y2 - $Y1 < $Radius * 2) {
+            $Radius = (int) floor((($Y2 - $Y1)) / 4);
+        }
+
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $this->Shadow = false;
+            $this->drawRoundedFilledRectangle(
+                $X1 + $this->ShadowX,
+                $Y1 + $this->ShadowY,
+                $X2 + $this->ShadowX,
+                $Y2 + $this->ShadowY,
+                $Radius,
+                [
+                    "R" => $this->ShadowR,
+                    "G" => $this->ShadowG,
+                    "B" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa
+                ]
+            );
+        }
+
+        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "NoBorder" => true];
+
+        if ($Radius <= 0) {
+            $this->drawFilledRectangle($X1, $Y1, $X2, $Y2, $Color);
+            return 0;
+        }
+
+        $YTop = $Y1 + $Radius;
+        $YBottom = $Y2 - $Radius;
+
+        $Step = 360 / (2 * PI * $Radius);
+        $Positions = [];
+        $Radius--;
+        $MinY = null;
+        $MaxY = null;
+        for ($i = 0; $i <= 90; $i = $i + $Step) {
+            $Xp1 = cos(($i + 180) * PI / 180) * $Radius + $X1 + $Radius;
+            $Xp2 = cos(((90 - $i) + 270) * PI / 180) * $Radius + $X2 - $Radius;
+            $Yp = (int) floor(sin(($i + 180) * PI / 180) * $Radius + $YTop);
+            if (null === $MinY || $Yp > $MinY) {
+                $MinY = $Yp;
+            }
+
+            if ($Xp1 <= floor($X1)) {
+                $Xp1++;
+            }
+            if ($Xp2 >= floor($X2)) {
+                $Xp2--;
+            }
+            $Xp1++;
+
+            if (!isset($Positions[$Yp])) {
+                $Positions[$Yp]["X1"] = $Xp1;
+                $Positions[$Yp]["X2"] = $Xp2;
+            } else {
+                $Positions[$Yp]["X1"] = ($Positions[$Yp]["X1"] + $Xp1) / 2;
+                $Positions[$Yp]["X2"] = ($Positions[$Yp]["X2"] + $Xp2) / 2;
+            }
+
+            $Xp1 = cos(($i + 90) * PI / 180) * $Radius + $X1 + $Radius;
+            $Xp2 = cos((90 - $i) * PI / 180) * $Radius + $X2 - $Radius;
+            $Yp = (int) floor(sin(($i + 90) * PI / 180) * $Radius + $YBottom);
+            if (null === $MaxY || $Yp < $MaxY) {
+                $MaxY = $Yp;
+            }
+
+            if ($Xp1 <= floor($X1)) {
+                $Xp1++;
+            }
+            if ($Xp2 >= floor($X2)) {
+                $Xp2--;
+            }
+            $Xp1++;
+
+            if (!isset($Positions[$Yp])) {
+                $Positions[$Yp]["X1"] = $Xp1;
+                $Positions[$Yp]["X2"] = $Xp2;
+            } else {
+                $Positions[$Yp]["X1"] = ($Positions[$Yp]["X1"] + $Xp1) / 2;
+                $Positions[$Yp]["X2"] = ($Positions[$Yp]["X2"] + $Xp2) / 2;
+            }
+        }
+
+        $ManualColor = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+        foreach ($Positions as $Yp => $Bounds) {
+            $X1 = (int) $Bounds["X1"];
+            $X1Dec = $this->getFirstDecimal($X1);
+            if ($X1Dec != 0) {
+                $X1 = (int) floor($X1) + 1;
+            }
+            $X2 = (int) $Bounds["X2"];
+            $X2Dec = $this->getFirstDecimal($X2);
+            if ($X2Dec != 0) {
+                $X2 = (int) floor($X2) - 1;
+            }
+            imageline($this->Picture, $X1, $Yp, $X2, $Yp, $ManualColor);
+        }
+        $this->drawFilledRectangle($X1, $MinY + 1, (int) floor($X2), $MaxY - 1, $Color);
+
+        $Radius++;
+        $this->drawRoundedRectangle(
+            $X1,
+            $Y1,
+            $X2 + 1,
+            $Y2 - 1,
+            $Radius,
+            ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+        );
+
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a rectangle
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param array $Format
+     */
+    public function drawRectangle($X1, $Y1, $X2, $Y2, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $NoAngle = isset($Format["NoAngle"]) ? $Format["NoAngle"] : false;
+
+        if ($X1 > $X2) {
+            list($X1, $X2) = [$X2, $X1];
+        }
+        if ($Y1 > $Y2) {
+            list($Y1, $Y2) = [$Y2, $Y1];
+        }
+
+        if ($this->Antialias) {
+            if ($NoAngle) {
+                $this->drawLine(
+                    $X1 + 1,
+                    $Y1,
+                    $X2 - 1,
+                    $Y1,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X2,
+                    $Y1 + 1,
+                    $X2,
+                    $Y2 - 1,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X2 - 1,
+                    $Y2,
+                    $X1 + 1,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X1,
+                    $Y1 + 1,
+                    $X1,
+                    $Y2 - 1,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+            } else {
+                $this->drawLine(
+                    $X1 + 1,
+                    $Y1,
+                    $X2 - 1,
+                    $Y1,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X2,
+                    $Y1,
+                    $X2,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X2 - 1,
+                    $Y2,
+                    $X1 + 1,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+                $this->drawLine(
+                    $X1,
+                    $Y1,
+                    $X1,
+                    $Y2,
+                    ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+                );
+            }
+        } else {
+            $Color = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+            imagerectangle($this->Picture, $X1, $Y1, $X2, $Y2, $Color);
+        }
+    }
+
+    /**
+     * Draw a filled rectangle
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param array $Format
+     */
+    public function drawFilledRectangle($X1, $Y1, $X2, $Y2, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : -1;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : -1;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : -1;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $NoAngle = isset($Format["NoAngle"]) ? $Format["NoAngle"] : null;
+        $Dash = isset($Format["Dash"]) ? $Format["Dash"] : false;
+        $DashStep = isset($Format["DashStep"]) ? $Format["DashStep"] : 4;
+        $DashR = isset($Format["DashR"]) ? $Format["DashR"] : 0;
+        $DashG = isset($Format["DashG"]) ? $Format["DashG"] : 0;
+        $DashB = isset($Format["DashB"]) ? $Format["DashB"] : 0;
+        $NoBorder = isset($Format["NoBorder"]) ? $Format["NoBorder"] : false;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        if ($X1 > $X2) {
+            list($X1, $X2) = [$X2, $X1];
+        }
+        if ($Y1 > $Y2) {
+            list($Y1, $Y2) = [$Y2, $Y1];
+        }
+
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $this->Shadow = false;
+            $this->drawFilledRectangle(
+                $X1 + $this->ShadowX,
+                $Y1 + $this->ShadowY,
+                $X2 + $this->ShadowX,
+                $Y2 + $this->ShadowY,
+                [
+                    "R" => $this->ShadowR,
+                    "G" => $this->ShadowG,
+                    "B" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa,
+                    "Ticks" => $Ticks,
+                    "NoAngle" => $NoAngle
+                ]
+            );
+        }
+
+        $Color = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+        if ($NoAngle) {
+            imagefilledrectangle($this->Picture, ceil($X1) + 1, ceil($Y1), floor($X2) - 1, floor($Y2), $Color);
+            imageline($this->Picture, ceil($X1), ceil($Y1) + 1, ceil($X1), floor($Y2) - 1, $Color);
+            imageline($this->Picture, floor($X2), ceil($Y1) + 1, floor($X2), floor($Y2) - 1, $Color);
+        } else {
+            imagefilledrectangle($this->Picture, ceil($X1), ceil($Y1), floor($X2), floor($Y2), $Color);
+        }
+        if ($Dash) {
+            if ($BorderR != -1) {
+                $iX1 = $X1 + 1;
+                $iY1 = $Y1 + 1;
+                $iX2 = $X2 - 1;
+                $iY2 = $Y2 - 1;
+            } else {
+                $iX1 = $X1;
+                $iY1 = $Y1;
+                $iX2 = $X2;
+                $iY2 = $Y2;
+            }
+
+            $Color = $this->allocateColor($this->Picture, $DashR, $DashG, $DashB, $Alpha);
+            $Y = $iY1 - $DashStep;
+            for ($X = $iX1; $X <= $iX2 + ($iY2 - $iY1); $X = $X + $DashStep) {
+                $Y = $Y + $DashStep;
+                if ($X > $iX2) {
+                    $Xa = $X - ($X - $iX2);
+                    $Ya = $iY1 + ($X - $iX2);
+                } else {
+                    $Xa = $X;
+                    $Ya = $iY1;
+                }
+                if ($Y > $iY2) {
+                    $Xb = $iX1 + ($Y - $iY2);
+                    $Yb = $Y - ($Y - $iY2);
+                } else {
+                    $Xb = $iX1;
+                    $Yb = $Y;
+                }
+                imageline($this->Picture, $Xa, $Ya, $Xb, $Yb, $Color);
+            }
+        }
+
+        if ($this->Antialias && !$NoBorder) {
+            if ($X1 < ceil($X1)) {
+                $AlphaA = $Alpha * (ceil($X1) - $X1);
+                $Color = $this->allocateColor($this->Picture, $R, $G, $B, $AlphaA);
+                imageline($this->Picture, ceil($X1) - 1, ceil($Y1), ceil($X1) - 1, floor($Y2), $Color);
+            }
+
+            if ($Y1 < ceil($Y1)) {
+                $AlphaA = $Alpha * (ceil($Y1) - $Y1);
+                $Color = $this->allocateColor($this->Picture, $R, $G, $B, $AlphaA);
+                imageline($this->Picture, ceil($X1), ceil($Y1) - 1, floor($X2), ceil($Y1) - 1, $Color);
+            }
+
+            if ($X2 > floor($X2)) {
+                $AlphaA = $Alpha * (.5 - ($X2 - floor($X2)));
+                $Color = $this->allocateColor($this->Picture, $R, $G, $B, $AlphaA);
+                imageline($this->Picture, floor($X2) + 1, ceil($Y1), floor($X2) + 1, floor($Y2), $Color);
+            }
+
+            if ($Y2 > floor($Y2)) {
+                $AlphaA = $Alpha * (.5 - ($Y2 - floor($Y2)));
+                $Color = $this->allocateColor($this->Picture, $R, $G, $B, $AlphaA);
+                imageline($this->Picture, ceil($X1), floor($Y2) + 1, floor($X2), floor($Y2) + 1, $Color);
+            }
+        }
+
+        if ($BorderR != -1) {
+            $this->drawRectangle(
+                $X1,
+                $Y1,
+                $X2,
+                $Y2,
+                [
+                    "R" => $BorderR,
+                    "G" => $BorderG,
+                    "B" => $BorderB,
+                    "Alpha" => $BorderAlpha,
+                    "Ticks" => $Ticks,
+                    "NoAngle" => $NoAngle
+                ]
+            );
+        }
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a rectangular marker of the specified size
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     */
+    public function drawRectangleMarker($X, $Y, array $Format = [])
+    {
+        $Size = isset($Format["Size"]) ? $Format["Size"] : 4;
+
+        $HalfSize = floor($Size / 2);
+        $this->drawFilledRectangle($X - $HalfSize, $Y - $HalfSize, $X + $HalfSize, $Y + $HalfSize, $Format);
+    }
+
+    /**
+     * Drawn a spline based on the bezier public function
+     * @param array $Coordinates
+     * @param array $Format
+     * @return array
+     */
+    public function drawSpline(array $Coordinates, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Force = isset($Format["Force"]) ? $Format["Force"] : 30;
+        $Forces = isset($Format["Forces"]) ? $Format["Forces"] : null;
+        $ShowC = isset($Format["ShowControl"]) ? $Format["ShowControl"] : false;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $PathOnly = isset($Format["PathOnly"]) ? $Format["PathOnly"] : false;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+
+        $Cpt = null;
+        $Mode = null;
+        $Result = [];
+        for ($i = 1; $i <= count($Coordinates) - 1; $i++) {
+            $X1 = $Coordinates[$i - 1][0];
+            $Y1 = $Coordinates[$i - 1][1];
+            $X2 = $Coordinates[$i][0];
+            $Y2 = $Coordinates[$i][1];
+
+            if ($Forces != null) {
+                $Force = $Forces[$i];
+            }
+
+            /* First segment */
+            if ($i == 1) {
+                $Xv1 = $X1;
+                $Yv1 = $Y1;
+            } else {
+                $Angle1 = $this->getAngle($XLast, $YLast, $X1, $Y1);
+                $Angle2 = $this->getAngle($X1, $Y1, $X2, $Y2);
+                $XOff = cos($Angle2 * PI / 180) * $Force + $X1;
+                $YOff = sin($Angle2 * PI / 180) * $Force + $Y1;
+
+                $Xv1 = cos($Angle1 * PI / 180) * $Force + $XOff;
+                $Yv1 = sin($Angle1 * PI / 180) * $Force + $YOff;
+            }
+
+            /* Last segment */
+            if ($i == count($Coordinates) - 1) {
+                $Xv2 = $X2;
+                $Yv2 = $Y2;
+            } else {
+                $Angle1 = $this->getAngle($X2, $Y2, $Coordinates[$i + 1][0], $Coordinates[$i + 1][1]);
+                $Angle2 = $this->getAngle($X1, $Y1, $X2, $Y2);
+                $XOff = cos(($Angle2 + 180) * PI / 180) * $Force + $X2;
+                $YOff = sin(($Angle2 + 180) * PI / 180) * $Force + $Y2;
+
+                $Xv2 = cos(($Angle1 + 180) * PI / 180) * $Force + $XOff;
+                $Yv2 = sin(($Angle1 + 180) * PI / 180) * $Force + $YOff;
+            }
+
+            $Path = $this->drawBezier($X1, $Y1, $X2, $Y2, $Xv1, $Yv1, $Xv2, $Yv2, $Format);
+            if ($PathOnly) {
+                $Result[] = $Path;
+            }
+
+            $XLast = $X1;
+            $YLast = $Y1;
+        }
+
+        return $Result;
+    }
+
+    /**
+     * Draw a bezier curve with two controls points
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param int $Xv1
+     * @param int $Yv1
+     * @param int $Xv2
+     * @param int $Yv2
+     * @param array $Format
+     * @return array
+     */
+    public function drawBezier($X1, $Y1, $X2, $Y2, $Xv1, $Yv1, $Xv2, $Yv2, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $ShowC = isset($Format["ShowControl"]) ? $Format["ShowControl"] : false;
+        $Segments = isset($Format["Segments"]) ? $Format["Segments"] : null;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $NoDraw = isset($Format["NoDraw"]) ? $Format["NoDraw"] : false;
+        $PathOnly = isset($Format["PathOnly"]) ? $Format["PathOnly"] : false;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+        $DrawArrow = isset($Format["DrawArrow"]) ? $Format["DrawArrow"] : false;
+        $ArrowSize = isset($Format["ArrowSize"]) ? $Format["ArrowSize"] : 10;
+        $ArrowRatio = isset($Format["ArrowRatio"]) ? $Format["ArrowRatio"] : .5;
+        $ArrowTwoHeads = isset($Format["ArrowTwoHeads"]) ? $Format["ArrowTwoHeads"] : false;
+
+        if ($Segments == null) {
+            $Length = $this->getLength($X1, $Y1, $X2, $Y2);
+            $Precision = ($Length * 125) / 1000;
+        } else {
+            $Precision = $Segments;
+        }
+        $P[0]["X"] = $X1;
+        $P[0]["Y"] = $Y1;
+        $P[1]["X"] = $Xv1;
+        $P[1]["Y"] = $Yv1;
+        $P[2]["X"] = $Xv2;
+        $P[2]["Y"] = $Yv2;
+        $P[3]["X"] = $X2;
+        $P[3]["Y"] = $Y2;
+
+        /* Compute the bezier points */
+        $Q = [];
+        $ID = 0;
+        for ($i = 0; $i <= $Precision; $i = $i + 1) {
+            $u = $i / $Precision;
+
+            $C = [];
+            $C[0] = (1 - $u) * (1 - $u) * (1 - $u);
+            $C[1] = ($u * 3) * (1 - $u) * (1 - $u);
+            $C[2] = 3 * $u * $u * (1 - $u);
+            $C[3] = $u * $u * $u;
+
+            for ($j = 0; $j <= 3; $j++) {
+                if (!isset($Q[$ID])) {
+                    $Q[$ID] = [];
+                }
+                if (!isset($Q[$ID]["X"])) {
+                    $Q[$ID]["X"] = 0;
+                }
+                if (!isset($Q[$ID]["Y"])) {
+                    $Q[$ID]["Y"] = 0;
+                }
+
+                $Q[$ID]["X"] = $Q[$ID]["X"] + $P[$j]["X"] * $C[$j];
+                $Q[$ID]["Y"] = $Q[$ID]["Y"] + $P[$j]["Y"] * $C[$j];
+            }
+            $ID++;
+        }
+        $Q[$ID]["X"] = $X2;
+        $Q[$ID]["Y"] = $Y2;
+
+        if (!$NoDraw) {
+            /* Display the control points */
+            if ($ShowC && !$PathOnly) {
+                $Xv1 = floor($Xv1);
+                $Yv1 = floor($Yv1);
+                $Xv2 = floor($Xv2);
+                $Yv2 = floor($Yv2);
+
+                $this->drawLine($X1, $Y1, $X2, $Y2, ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 30]);
+
+                $MyMarkerSettings = [
+                    "R" => 255,
+                    "G" => 0,
+                    "B" => 0,
+                    "BorderR" => 255,
+                    "BorderB" => 255,
+                    "BorderG" => 255,
+                    "Size" => 4
+                ];
+                $this->drawRectangleMarker($Xv1, $Yv1, $MyMarkerSettings);
+                $this->drawText($Xv1 + 4, $Yv1, "v1");
+                $MyMarkerSettings = [
+                    "R" => 0,
+                    "G" => 0,
+                    "B" => 255,
+                    "BorderR" => 255,
+                    "BorderB" => 255,
+                    "BorderG" => 255,
+                    "Size" => 4
+                ];
+                $this->drawRectangleMarker($Xv2, $Yv2, $MyMarkerSettings);
+                $this->drawText($Xv2 + 4, $Yv2, "v2");
+            }
+
+            /* Draw the bezier */
+            $LastX = null;
+            $LastY = null;
+            $Cpt = null;
+            $Mode = null;
+            $ArrowS = [];
+            foreach ($Q as $Point) {
+                $X = $Point["X"];
+                $Y = $Point["Y"];
+
+                /* Get the first segment */
+                if (!count($ArrowS) && $LastX != null && $LastY != null) {
+                    $ArrowS["X2"] = $LastX;
+                    $ArrowS["Y2"] = $LastY;
+                    $ArrowS["X1"] = $X;
+                    $ArrowS["Y1"] = $Y;
+                }
+
+                if ($LastX != null && $LastY != null && !$PathOnly) {
+                    list($Cpt, $Mode) = $this->drawLine(
+                        $LastX,
+                        $LastY,
+                        $X,
+                        $Y,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha,
+                            "Ticks" => $Ticks,
+                            "Cpt" => $Cpt,
+                            "Mode" => $Mode,
+                            "Weight" => $Weight
+                        ]
+                    );
+                }
+                /* Get the last segment */
+                $ArrowE["X1"] = $LastX;
+                $ArrowE["Y1"] = $LastY;
+                $ArrowE["X2"] = $X;
+                $ArrowE["Y2"] = $Y;
+
+                $LastX = $X;
+                $LastY = $Y;
+            }
+
+            if ($DrawArrow && !$PathOnly) {
+                $ArrowSettings = [
+                    "FillR" => $R,
+                    "FillG" => $G,
+                    "FillB" => $B,
+                    "Alpha" => $Alpha,
+                    "Size" => $ArrowSize,
+                    "Ratio" => $ArrowRatio
+                ];
+                if ($ArrowTwoHeads) {
+                    $this->drawArrow($ArrowS["X1"], $ArrowS["Y1"], $ArrowS["X2"], $ArrowS["Y2"], $ArrowSettings);
+                }
+                $this->drawArrow($ArrowE["X1"], $ArrowE["Y1"], $ArrowE["X2"], $ArrowE["Y2"], $ArrowSettings);
+            }
+        }
+        return $Q;
+    }
+
+    /**
+     * Draw a line between two points
+     * @param int|float $X1
+     * @param int|float $Y1
+     * @param int|float $X2
+     * @param int|float $Y2
+     * @param array $Format
+     * @return array|int
+     */
+    public function drawLine($X1, $Y1, $X2, $Y2, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $Cpt = isset($Format["Cpt"]) ? $Format["Cpt"] : 1;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : 1;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+        $Threshold = isset($Format["Threshold"]) ? $Format["Threshold"] : null;
+
+        if ($this->Antialias == false && $Ticks == null) {
+            if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+                $ShadowColor = $this->allocateColor(
+                    $this->Picture,
+                    $this->ShadowR,
+                    $this->ShadowG,
+                    $this->ShadowB,
+                    $this->Shadowa
+                );
+                imageline(
+                    $this->Picture,
+                    intval($X1 + $this->ShadowX),
+                    intval($Y1 + $this->ShadowY),
+                    intval($X2 + $this->ShadowX),
+                    intval($Y2 + $this->ShadowY),
+                    $ShadowColor
+                );
+            }
+
+            $Color = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+            imageline($this->Picture, (int) $X1, (int) $Y1, (int) $X2, (int) $Y2, $Color);
+            return 0;
+        }
+
+        $Distance = sqrt(($X2 - $X1) * ($X2 - $X1) + ($Y2 - $Y1) * ($Y2 - $Y1));
+        if ($Distance == 0) {
+            return -1;
+        }
+
+        /* Derivative algorithm for overweighted lines, re-route to polygons primitives */
+        if ($Weight != null) {
+            $Angle = $this->getAngle($X1, $Y1, $X2, $Y2);
+            $PolySettings = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "BorderAlpha" => $Alpha];
+
+            if ($Ticks == null) {
+                $Points = [];
+                $Points[] = cos(deg2rad($Angle - 90)) * $Weight + $X1;
+                $Points[] = sin(deg2rad($Angle - 90)) * $Weight + $Y1;
+                $Points[] = cos(deg2rad($Angle + 90)) * $Weight + $X1;
+                $Points[] = sin(deg2rad($Angle + 90)) * $Weight + $Y1;
+                $Points[] = cos(deg2rad($Angle + 90)) * $Weight + $X2;
+                $Points[] = sin(deg2rad($Angle + 90)) * $Weight + $Y2;
+                $Points[] = cos(deg2rad($Angle - 90)) * $Weight + $X2;
+                $Points[] = sin(deg2rad($Angle - 90)) * $Weight + $Y2;
+
+                $this->drawPolygon($Points, $PolySettings);
+            } else {
+                for ($i = 0; $i <= $Distance; $i = $i + $Ticks * 2) {
+                    $Xa = (($X2 - $X1) / $Distance) * $i + $X1;
+                    $Ya = (($Y2 - $Y1) / $Distance) * $i + $Y1;
+                    $Xb = (($X2 - $X1) / $Distance) * ($i + $Ticks) + $X1;
+                    $Yb = (($Y2 - $Y1) / $Distance) * ($i + $Ticks) + $Y1;
+
+                    $Points = [];
+                    $Points[] = cos(deg2rad($Angle - 90)) * $Weight + $Xa;
+                    $Points[] = sin(deg2rad($Angle - 90)) * $Weight + $Ya;
+                    $Points[] = cos(deg2rad($Angle + 90)) * $Weight + $Xa;
+                    $Points[] = sin(deg2rad($Angle + 90)) * $Weight + $Ya;
+                    $Points[] = cos(deg2rad($Angle + 90)) * $Weight + $Xb;
+                    $Points[] = sin(deg2rad($Angle + 90)) * $Weight + $Yb;
+                    $Points[] = cos(deg2rad($Angle - 90)) * $Weight + $Xb;
+                    $Points[] = sin(deg2rad($Angle - 90)) * $Weight + $Yb;
+
+                    $this->drawPolygon($Points, $PolySettings);
+                }
+            }
+
+            return 1;
+        }
+
+        $XStep = ($X2 - $X1) / $Distance;
+        $YStep = ($Y2 - $Y1) / $Distance;
+
+        for ($i = 0; $i <= $Distance; $i++) {
+            $X = $i * $XStep + $X1;
+            $Y = $i * $YStep + $Y1;
+
+            $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+
+            if ($Threshold != null) {
+                foreach ($Threshold as $Key => $Parameters) {
+                    if ($Y <= $Parameters["MinX"] && $Y >= $Parameters["MaxX"]) {
+                        if (isset($Parameters["R"])) {
+                            $RT = $Parameters["R"];
+                        } else {
+                            $RT = 0;
+                        }
+                        if (isset($Parameters["G"])) {
+                            $GT = $Parameters["G"];
+                        } else {
+                            $GT = 0;
+                        }
+                        if (isset($Parameters["B"])) {
+                            $BT = $Parameters["B"];
+                        } else {
+                            $BT = 0;
+                        }
+                        if (isset($Parameters["Alpha"])) {
+                            $AlphaT = $Parameters["Alpha"];
+                        } else {
+                            $AlphaT = 0;
+                        }
+                        $Color = ["R" => $RT, "G" => $GT, "B" => $BT, "Alpha" => $AlphaT];
+                    }
+                }
+            }
+
+            if ($Ticks != null) {
+                if ($Cpt % $Ticks == 0) {
+                    $Cpt = 0;
+                    if ($Mode == 1) {
+                        $Mode = 0;
+                    } else {
+                        $Mode = 1;
+                    }
+                }
+
+                if ($Mode == 1) {
+                    $this->drawAntialiasPixel($X, $Y, $Color);
+                }
+                $Cpt++;
+            } else {
+                $this->drawAntialiasPixel($X, $Y, $Color);
+            }
+        }
+
+        return [$Cpt, $Mode];
+    }
+
+    /**
+     * Draw a circle
+     * @param int $Xc
+     * @param int $Yc
+     * @param int|float $Height
+     * @param int|float $Width
+     * @param array $Format
+     */
+    public function drawCircle($Xc, $Yc, $Height, $Width, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+
+        $Height = abs($Height);
+        $Width = abs($Width);
+
+        if ($Height == 0) {
+            $Height = 1;
+        }
+        if ($Width == 0) {
+            $Width = 1;
+        }
+        $Xc = floor($Xc);
+        $Yc = floor($Yc);
+
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $this->Shadow = false;
+            $this->drawCircle(
+                $Xc + $this->ShadowX,
+                $Yc + $this->ShadowY,
+                $Height,
+                $Width,
+                [
+                    "R" => $this->ShadowR,
+                    "G" => $this->ShadowG,
+                    "B" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa,
+                    "Ticks" => $Ticks
+                ]
+            );
+        }
+
+        if ($Width == 0) {
+            $Width = $Height;
+        }
+        if ($R < 0) {
+            $R = 0;
+        } if ($R > 255) {
+            $R = 255;
+        }
+        if ($G < 0) {
+            $G = 0;
+        } if ($G > 255) {
+            $G = 255;
+        }
+        if ($B < 0) {
+            $B = 0;
+        } if ($B > 255) {
+            $B = 255;
+        }
+
+        $Step = 360 / (2 * PI * max($Width, $Height));
+        $Mode = 1;
+        $Cpt = 1;
+        for ($i = 0; $i <= 360; $i = $i + $Step) {
+            $X = cos($i * PI / 180) * $Height + $Xc;
+            $Y = sin($i * PI / 180) * $Width + $Yc;
+
+            if ($Ticks != null) {
+                if ($Cpt % $Ticks == 0) {
+                    $Cpt = 0;
+                    if ($Mode == 1) {
+                        $Mode = 0;
+                    } else {
+                        $Mode = 1;
+                    }
+                }
+
+                if ($Mode == 1) {
+                    $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+                }
+                $Cpt++;
+            } else {
+                $this->drawAntialiasPixel($X, $Y, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+            }
+        }
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a filled circle
+     * @param int $X
+     * @param int $Y
+     * @param int|float $Radius
+     * @param array $Format
+     */
+    public function drawFilledCircle($X, $Y, $Radius, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : -1;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : -1;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : -1;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+
+        if ($Radius == 0) {
+            $Radius = 1;
+        }
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+        $X = (int) floor($X);
+        $Y = (int) floor($Y);
+
+        $Radius = abs($Radius);
+
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $this->Shadow = false;
+            $this->drawFilledCircle(
+                $X + $this->ShadowX,
+                $Y + $this->ShadowY,
+                $Radius,
+                [
+                    "R" => $this->ShadowR,
+                    "G" => $this->ShadowG,
+                    "B" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa,
+                    "Ticks" => $Ticks
+                ]
+            );
+        }
+
+        $this->Mask = [];
+        $Color = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+        for ($i = 0; $i <= $Radius * 2; $i++) {
+            $Slice = sqrt($Radius * $Radius - ($Radius - $i) * ($Radius - $i));
+            $XPos = (int) floor($Slice);
+            $YPos = (int) ($Y + $i - $Radius);
+
+            $this->Mask[$X - $XPos][$YPos] = true;
+            $this->Mask[$X + $XPos][$YPos] = true;
+            imageline($this->Picture, $X - $XPos, $YPos, $X + $XPos, $YPos, $Color);
+        }
+        if ($this->Antialias) {
+            $this->drawCircle(
+                $X,
+                $Y,
+                $Radius,
+                $Radius,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks]
+            );
+        }
+        $this->Mask = [];
+
+        if ($BorderR != -1) {
+            $this->drawCircle(
+                $X,
+                $Y,
+                $Radius,
+                $Radius,
+                [
+                    "R" => $BorderR,
+                    "G" => $BorderG,
+                    "B" => $BorderB,
+                    "Alpha" => $BorderAlpha,
+                    "Ticks" => $Ticks
+                ]
+            );
+        }
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Write text
+     * @param int|float $X
+     * @param int|float $Y
+     * @param string $Text
+     * @param array $Format
+     * @return array
+     */
+    public function drawText($X, $Y, $Text, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : $this->FontColorR;
+        $G = isset($Format["G"]) ? $Format["G"] : $this->FontColorG;
+        $B = isset($Format["B"]) ? $Format["B"] : $this->FontColorB;
+        $Angle = isset($Format["Angle"]) ? $Format["Angle"] : 0;
+        $Align = isset($Format["Align"]) ? $Format["Align"] : TEXT_ALIGN_BOTTOMLEFT;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : $this->FontColorA;
+        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
+        $ShowOrigine = isset($Format["ShowOrigine"]) ? $Format["ShowOrigine"] : false;
+        $TOffset = isset($Format["TOffset"]) ? $Format["TOffset"] : 2;
+        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : false;
+        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 6;
+        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : false;
+        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 6;
+        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 255;
+        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 255;
+        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 255;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 50;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
+        $BoxBorderR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
+        $BoxBorderG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
+        $BoxBorderB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
+        $BoxBorderAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 50;
+        $NoShadow = isset($Format["NoShadow"]) ? $Format["NoShadow"] : false;
+
+        $Shadow = $this->Shadow;
+        if ($NoShadow) {
+            $this->Shadow = false;
+        }
+
+        if ($BoxSurrounding != "") {
+            $BoxBorderR = $BoxR - $BoxSurrounding;
+            $BoxBorderG = $BoxG - $BoxSurrounding;
+            $BoxBorderB = $BoxB - $BoxSurrounding;
+            $BoxBorderAlpha = $BoxAlpha;
+        }
+
+        if ($ShowOrigine) {
+            $MyMarkerSettings = [
+                "R" => 255,
+                "G" => 0,
+                "B" => 0,
+                "BorderR" => 255,
+                "BorderB" => 255,
+                "BorderG" => 255,
+                "Size" => 4
+            ];
+            $this->drawRectangleMarker($X, $Y, $MyMarkerSettings);
+        }
+
+        $TxtPos = $this->getTextBox($X, $Y, $FontName, $FontSize, $Angle, $Text);
+
+        if ($DrawBox && ($Angle == 0 || $Angle == 90 || $Angle == 180 || $Angle == 270)) {
+            $T[0]["X"] = 0;
+            $T[0]["Y"] = 0;
+            $T[1]["X"] = 0;
+            $T[1]["Y"] = 0;
+            $T[2]["X"] = 0;
+            $T[2]["Y"] = 0;
+            $T[3]["X"] = 0;
+            $T[3]["Y"] = 0;
+            if ($Angle == 0) {
+                $T[0]["X"] = -$TOffset;
+                $T[0]["Y"] = $TOffset;
+                $T[1]["X"] = $TOffset;
+                $T[1]["Y"] = $TOffset;
+                $T[2]["X"] = $TOffset;
+                $T[2]["Y"] = -$TOffset;
+                $T[3]["X"] = -$TOffset;
+                $T[3]["Y"] = -$TOffset;
+            }
+
+            $X1 = min($TxtPos[0]["X"], $TxtPos[1]["X"], $TxtPos[2]["X"], $TxtPos[3]["X"]) - $BorderOffset + 3;
+            $Y1 = min($TxtPos[0]["Y"], $TxtPos[1]["Y"], $TxtPos[2]["Y"], $TxtPos[3]["Y"]) - $BorderOffset;
+            $X2 = max($TxtPos[0]["X"], $TxtPos[1]["X"], $TxtPos[2]["X"], $TxtPos[3]["X"]) + $BorderOffset + 3;
+            $Y2 = max($TxtPos[0]["Y"], $TxtPos[1]["Y"], $TxtPos[2]["Y"], $TxtPos[3]["Y"]) + $BorderOffset - 3;
+
+            $X1 = $X1 - $TxtPos[$Align]["X"] + $X + $T[0]["X"];
+            $Y1 = $Y1 - $TxtPos[$Align]["Y"] + $Y + $T[0]["Y"];
+            $X2 = $X2 - $TxtPos[$Align]["X"] + $X + $T[0]["X"];
+            $Y2 = $Y2 - $TxtPos[$Align]["Y"] + $Y + $T[0]["Y"];
+
+            $Settings = [
+                "R" => $BoxR,
+                "G" => $BoxG,
+                "B" => $BoxB,
+                "Alpha" => $BoxAlpha,
+                "BorderR" => $BoxBorderR,
+                "BorderG" => $BoxBorderG,
+                "BorderB" => $BoxBorderB,
+                "BorderAlpha" => $BoxBorderAlpha
+            ];
+
+            if ($BoxRounded) {
+                $this->drawRoundedFilledRectangle($X1, $Y1, $X2, $Y2, $RoundedRadius, $Settings);
+            } else {
+                $this->drawFilledRectangle($X1, $Y1, $X2, $Y2, $Settings);
+            }
+        }
+
+        $X = (int) ($X - $TxtPos[$Align]["X"] + $X);
+        $Y = (int) ($Y - $TxtPos[$Align]["Y"] + $Y);
+
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $C_ShadowColor = $this->allocateColor(
+                $this->Picture,
+                $this->ShadowR,
+                $this->ShadowG,
+                $this->ShadowB,
+                $this->Shadowa
+            );
+            imagettftext(
+                $this->Picture,
+                $FontSize,
+                $Angle,
+                (int) ($X + $this->ShadowX),
+                (int) ($Y + $this->ShadowY),
+                (int) $C_ShadowColor,
+                $FontName,
+                $Text
+            );
+        }
+
+        $C_TextColor = $this->AllocateColor($this->Picture, $R, $G, $B, $Alpha);
+        imagettftext($this->Picture, $FontSize, $Angle, $X, $Y, $C_TextColor, $FontName, $Text);
+
+        $this->Shadow = $Shadow;
+
+        return $TxtPos;
+    }
+
+    /**
+     * Draw a gradient within a defined area
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param int $Direction
+     * @param array $Format
+     * @return null|integer
+     */
+    public function drawGradientArea($X1, $Y1, $X2, $Y2, $Direction, array $Format = [])
+    {
+        $StartR = isset($Format["StartR"]) ? $Format["StartR"] : 90;
+        $StartG = isset($Format["StartG"]) ? $Format["StartG"] : 90;
+        $StartB = isset($Format["StartB"]) ? $Format["StartB"] : 90;
+        $EndR = isset($Format["EndR"]) ? $Format["EndR"] : 0;
+        $EndG = isset($Format["EndG"]) ? $Format["EndG"] : 0;
+        $EndB = isset($Format["EndB"]) ? $Format["EndB"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Levels = isset($Format["Levels"]) ? $Format["Levels"] : null;
+
+        $Shadow = $this->Shadow;
+        $this->Shadow = false;
+
+        if ($StartR == $EndR && $StartG == $EndG && $StartB == $EndB) {
+            $this->drawFilledRectangle(
+                $X1,
+                $Y1,
+                $X2,
+                $Y2,
+                ["R" => $StartR, "G" => $StartG, "B" => $StartB, "Alpha" => $Alpha]
+            );
+            return 0;
+        }
+
+        if ($Levels != null) {
+            $EndR = $StartR + $Levels;
+            $EndG = $StartG + $Levels;
+            $EndB = $StartB + $Levels;
+        }
+
+        if ($X1 > $X2) {
+            list($X1, $X2) = [$X2, $X1];
+        }
+        if ($Y1 > $Y2) {
+            list($Y1, $Y2) = [$Y2, $Y1];
+        }
+
+        if ($Direction == DIRECTION_VERTICAL) {
+            $Width = abs($Y2 - $Y1);
+        }
+        if ($Direction == DIRECTION_HORIZONTAL) {
+            $Width = abs($X2 - $X1);
+        }
+
+        $Step = max(abs($EndR - $StartR), abs($EndG - $StartG), abs($EndB - $StartB));
+        $StepSize = $Width / $Step;
+        $RStep = ($EndR - $StartR) / $Step;
+        $GStep = ($EndG - $StartG) / $Step;
+        $BStep = ($EndB - $StartB) / $Step;
+
+        $R = $StartR;
+        $G = $StartG;
+        $B = $StartB;
+        switch ($Direction) {
+            case DIRECTION_VERTICAL:
+                $StartY = $Y1;
+                $EndY = floor($Y2) + 1;
+                $LastY2 = $StartY;
+                for ($i = 0; $i <= $Step; $i++) {
+                    $Y2 = floor($StartY + ($i * $StepSize));
+
+                    if ($Y2 > $EndY) {
+                        $Y2 = $EndY;
+                    }
+                    if (($Y1 != $Y2 && $Y1 < $Y2) || $Y2 == $EndY) {
+                        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                        $this->drawFilledRectangle($X1, $Y1, $X2, $Y2, $Color);
+                        $LastY2 = max($LastY2, $Y2);
+                        $Y1 = $Y2 + 1;
+                    }
+                    $R = $R + $RStep;
+                    $G = $G + $GStep;
+                    $B = $B + $BStep;
+                }
+                if ($LastY2 < $EndY && isset($Color)) {
+                    for ($i = $LastY2 + 1; $i <= $EndY; $i++) {
+                        $this->drawLine($X1, $i, $X2, $i, $Color);
+                    }
+                }
+                break;
+
+            case DIRECTION_HORIZONTAL:
+                $StartX = $X1;
+                $EndX = $X2;
+                for ($i = 0; $i <= $Step; $i++) {
+                    $X2 = floor($StartX + ($i * $StepSize));
+
+                    if ($X2 > $EndX) {
+                        $X2 = $EndX;
+                    }
+                    if (($X1 != $X2 && $X1 < $X2) || $X2 == $EndX) {
+                        $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                        $this->drawFilledRectangle($X1, $Y1, $X2, $Y2, $Color);
+                        $X1 = $X2 + 1;
+                    }
+                    $R = $R + $RStep;
+                    $G = $G + $GStep;
+                    $B = $B + $BStep;
+                }
+                if ($X2 < $EndX && isset($Color)) {
+                    $this->drawFilledRectangle($X2, $Y1, $EndX, $Y2, $Color);
+                }
+                break;
+        }
+
+        $this->Shadow = $Shadow;
+    }
+
+    /**
+     * Draw an aliased pixel
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     * @return int|null
+     */
+    public function drawAntialiasPixel($X, $Y, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+
+        if ($X < 0 || $Y < 0 || $X >= $this->XSize || $Y >= $this->YSize) {
+            return -1;
+        }
+
+        if ($R < 0) {
+            $R = 0;
+        }
+        if ($R > 255) {
+            $R = 255;
+        }
+        if ($G < 0) {
+            $G = 0;
+        }
+        if ($G > 255) {
+            $G = 255;
+        }
+        if ($B < 0) {
+            $B = 0;
+        }
+        if ($B > 255) {
+            $B = 255;
+        }
+
+        if (!$this->Antialias) {
+            if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+                $ShadowColor = $this->allocateColor(
+                    $this->Picture,
+                    $this->ShadowR,
+                    $this->ShadowG,
+                    $this->ShadowB,
+                    $this->Shadowa
+                );
+                imagesetpixel($this->Picture, intval($X + $this->ShadowX), intval($Y + $this->ShadowY), $ShadowColor);
+            }
+
+            $PlotColor = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+            imagesetpixel($this->Picture, (int) $X, (int) $Y, $PlotColor);
+
+            return 0;
+        }
+
+        $Xi = floor($X);
+        $Yi = floor($Y);
+
+        if ($Xi == $X && $Yi == $Y) {
+            if ($Alpha == 100) {
+                $this->drawAlphaPixel($X, $Y, 100, $R, $G, $B);
+            } else {
+                $this->drawAlphaPixel($X, $Y, $Alpha, $R, $G, $B);
+            }
+        } else {
+            $Alpha1 = (((1 - ($X - floor($X))) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
+            if ($Alpha1 > $this->AntialiasQuality) {
+                $this->drawAlphaPixel($Xi, $Yi, $Alpha1, $R, $G, $B);
+            }
+
+            $Alpha2 = ((($X - floor($X)) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
+            if ($Alpha2 > $this->AntialiasQuality) {
+                $this->drawAlphaPixel($Xi + 1, $Yi, $Alpha2, $R, $G, $B);
+            }
+
+            $Alpha3 = (((1 - ($X - floor($X))) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
+            if ($Alpha3 > $this->AntialiasQuality) {
+                $this->drawAlphaPixel($Xi, $Yi + 1, $Alpha3, $R, $G, $B);
+            }
+
+            $Alpha4 = ((($X - floor($X)) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
+            if ($Alpha4 > $this->AntialiasQuality) {
+                $this->drawAlphaPixel($Xi + 1, $Yi + 1, $Alpha4, $R, $G, $B);
+            }
+        }
+    }
+
+    /**
+     * Draw a semi-transparent pixel
+     * @param int $X
+     * @param int $Y
+     * @param int $Alpha
+     * @param int $R
+     * @param int $G
+     * @param int $B
+     * @return null|integer
+     */
+    public function drawAlphaPixel($X, $Y, $Alpha, $R, $G, $B)
+    {
+        if (isset($this->Mask[$X]) && isset($this->Mask[$X][$Y])) {
+            return 0;
+        }
+
+        if ($X < 0 || $Y < 0 || $X >= $this->XSize || $Y >= $this->YSize) {
+            return -1;
+        }
+        if ($R < 0) {
+            $R = 0;
+        } if ($R > 255) {
+            $R = 255;
+        }
+        if ($G < 0) {
+            $G = 0;
+        } if ($G > 255) {
+            $G = 255;
+        }
+        if ($B < 0) {
+            $B = 0;
+        } if ($B > 255) {
+            $B = 255;
+        }
+
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $AlphaFactor = floor(($Alpha / 100) * $this->Shadowa);
+            $ShadowColor = $this->allocateColor(
+                $this->Picture,
+                $this->ShadowR,
+                $this->ShadowG,
+                $this->ShadowB,
+                $AlphaFactor
+            );
+            imagesetpixel(
+                $this->Picture,
+                (int) ($X + $this->ShadowX),
+                (int) ($Y + $this->ShadowY),
+                $ShadowColor
+            );
+        }
+
+        $C_Aliased = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+        imagesetpixel($this->Picture, (int) $X, (int) $Y, $C_Aliased);
+    }
+
+    /**
+     * Load a PNG file and draw it over the chart
+     * @param int $X
+     * @param int $Y
+     * @param string $FileName
+     */
+    public function drawFromPNG($X, $Y, $FileName)
+    {
+        $this->drawFromPicture(1, $FileName, $X, $Y);
+    }
+
+    /**
+     * Load a GIF file and draw it over the chart
+     * @param int $X
+     * @param int $Y
+     * @param string $FileName
+     */
+    public function drawFromGIF($X, $Y, $FileName)
+    {
+        $this->drawFromPicture(2, $FileName, $X, $Y);
+    }
+
+    /**
+     * Load a JPEG file and draw it over the chart
+     * @param int $X
+     * @param int $Y
+     * @param string $FileName
+     */
+    public function drawFromJPG($X, $Y, $FileName)
+    {
+        $this->drawFromPicture(3, $FileName, $X, $Y);
+    }
+
+    /**
+     * Generic loader public function for external pictures
+     * @param int $PicType
+     * @param string $FileName
+     * @param int $X
+     * @param int $Y
+     * @return null|integer
+     */
+    public function drawFromPicture($PicType, $FileName, $X, $Y)
+    {
+        $X = (int) $X;
+        $Y = (int) $Y;
+
+        if (file_exists($FileName)) {
+            list($Width, $Height) = $this->getPicInfo($FileName);
+
+            if ($PicType == 1) {
+                $Raster = imagecreatefrompng($FileName);
+            } elseif ($PicType == 2) {
+                $Raster = imagecreatefromgif($FileName);
+            } elseif ($PicType == 3) {
+                $Raster = imagecreatefromjpeg($FileName);
+            } else {
+                return 0;
+            }
+
+            $RestoreShadow = $this->Shadow;
+            if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+                $this->Shadow = false;
+                if ($PicType == 3) {
+                    $this->drawFilledRectangle(
+                        $X + $this->ShadowX,
+                        $Y + $this->ShadowY,
+                        $X + $Width + $this->ShadowX,
+                        $Y + $Height + $this->ShadowY,
+                        [
+                            "R" => $this->ShadowR,
+                            "G" => $this->ShadowG,
+                            "B" => $this->ShadowB,
+                            "Alpha" => $this->Shadowa
+                        ]
+                    );
+                } else {
+                    $TranparentID = imagecolortransparent($Raster);
+                    for ($Xc = 0; $Xc <= $Width - 1; $Xc++) {
+                        for ($Yc = 0; $Yc <= $Height - 1; $Yc++) {
+                            $RGBa = imagecolorat($Raster, $Xc, $Yc);
+                            $Values = imagecolorsforindex($Raster, $RGBa);
+                            if ($Values["alpha"] < 120) {
+                                $AlphaFactor = floor(
+                                    ($this->Shadowa / 100) * ((100 / 127) * (127 - $Values["alpha"]))
+                                );
+                                $this->drawAlphaPixel(
+                                    $X + $Xc + $this->ShadowX,
+                                    $Y + $Yc + $this->ShadowY,
+                                    $AlphaFactor,
+                                    $this->ShadowR,
+                                    $this->ShadowG,
+                                    $this->ShadowB
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+            $this->Shadow = $RestoreShadow;
+
+            imagecopy($this->Picture, $Raster, $X, $Y, 0, 0, $Width, $Height);
+            imagedestroy($Raster);
+        }
+    }
+
+    /**
+     * Draw an arrow
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @param array $Format
+     */
+    public function drawArrow($X1, $Y1, $X2, $Y2, array $Format = [])
+    {
+        $FillR = isset($Format["FillR"]) ? $Format["FillR"] : 0;
+        $FillG = isset($Format["FillG"]) ? $Format["FillG"] : 0;
+        $FillB = isset($Format["FillB"]) ? $Format["FillB"] : 0;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $FillR;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $FillG;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $FillB;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Size = isset($Format["Size"]) ? $Format["Size"] : 10;
+        $Ratio = isset($Format["Ratio"]) ? $Format["Ratio"] : .5;
+        $TwoHeads = isset($Format["TwoHeads"]) ? $Format["TwoHeads"] : false;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : false;
+
+        /* Calculate the line angle */
+        $Angle = $this->getAngle($X1, $Y1, $X2, $Y2);
+
+        /* Override Shadow support, this will be managed internally */
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow && $this->ShadowX != 0 && $this->ShadowY != 0) {
+            $this->Shadow = false;
+            $this->drawArrow(
+                $X1 + $this->ShadowX,
+                $Y1 + $this->ShadowY,
+                $X2 + $this->ShadowX,
+                $Y2 + $this->ShadowY,
+                [
+                    "FillR" => $this->ShadowR,
+                    "FillG" => $this->ShadowG,
+                    "FillB" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa,
+                    "Size" => $Size,
+                    "Ratio" => $Ratio,
+                    "TwoHeads" => $TwoHeads,
+                    "Ticks" => $Ticks
+                ]
+            );
+        }
+
+        /* Draw the 1st Head */
+        $TailX = cos(($Angle - 180) * PI / 180) * $Size + $X2;
+        $TailY = sin(($Angle - 180) * PI / 180) * $Size + $Y2;
+
+        $Points = [];
+        $Points[] = $X2;
+        $Points[] = $Y2;
+        $Points[] = cos(($Angle - 90) * PI / 180) * $Size * $Ratio + $TailX;
+        $Points[] = sin(($Angle - 90) * PI / 180) * $Size * $Ratio + $TailY;
+        $Points[] = cos(($Angle - 270) * PI / 180) * $Size * $Ratio + $TailX;
+        $Points[] = sin(($Angle - 270) * PI / 180) * $Size * $Ratio + $TailY;
+        $Points[] = $X2;
+        $Points[] = $Y2;
+
+        /* Visual correction */
+        if ($Angle == 180 || $Angle == 360) {
+            $Points[4] = $Points[2];
+        }
+        if ($Angle == 90 || $Angle == 270) {
+            $Points[5] = $Points[3];
+        }
+
+        $ArrowColor = $this->allocateColor($this->Picture, $FillR, $FillG, $FillB, $Alpha);
+        $this->imageFilledPolygonWrapper($this->Picture, $Points, 4, $ArrowColor);
+
+        $this->drawLine(
+            $Points[0],
+            $Points[1],
+            $Points[2],
+            $Points[3],
+            ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+        );
+        $this->drawLine(
+            $Points[2],
+            $Points[3],
+            $Points[4],
+            $Points[5],
+            ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+        );
+        $this->drawLine(
+            $Points[0],
+            $Points[1],
+            $Points[4],
+            $Points[5],
+            ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+        );
+
+        /* Draw the second head */
+        if ($TwoHeads) {
+            $Angle = $this->getAngle($X2, $Y2, $X1, $Y1);
+
+            $TailX2 = cos(($Angle - 180) * PI / 180) * $Size + $X1;
+            $TailY2 = sin(($Angle - 180) * PI / 180) * $Size + $Y1;
+
+            $Points = [];
+            $Points[] = $X1;
+            $Points[] = $Y1;
+            $Points[] = cos(($Angle - 90) * PI / 180) * $Size * $Ratio + $TailX2;
+            $Points[] = sin(($Angle - 90) * PI / 180) * $Size * $Ratio + $TailY2;
+            $Points[] = cos(($Angle - 270) * PI / 180) * $Size * $Ratio + $TailX2;
+            $Points[] = sin(($Angle - 270) * PI / 180) * $Size * $Ratio + $TailY2;
+            $Points[] = $X1;
+            $Points[] = $Y1;
+
+            /* Visual correction */
+            if ($Angle == 180 || $Angle == 360) {
+                $Points[4] = $Points[2];
+            }
+            if ($Angle == 90 || $Angle == 270) {
+                $Points[5] = $Points[3];
+            }
+
+            $ArrowColor = $this->allocateColor($this->Picture, $FillR, $FillG, $FillB, $Alpha);
+            $this->imageFilledPolygonWrapper($this->Picture, $Points, 4, $ArrowColor);
+
+            $this->drawLine(
+                $Points[0],
+                $Points[1],
+                $Points[2],
+                $Points[3],
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+            );
+            $this->drawLine(
+                $Points[2],
+                $Points[3],
+                $Points[4],
+                $Points[5],
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+            );
+            $this->drawLine(
+                $Points[0],
+                $Points[1],
+                $Points[4],
+                $Points[5],
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+            );
+
+            $this->drawLine(
+                $TailX,
+                $TailY,
+                $TailX2,
+                $TailY2,
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha, "Ticks" => $Ticks]
+            );
+        } else {
+            $this->drawLine(
+                $X1,
+                $Y1,
+                $TailX,
+                $TailY,
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha, "Ticks" => $Ticks]
+            );
+        }
+        /* Re-enable shadows */
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a label with associated arrow
+     * @param int $X1
+     * @param int $Y1
+     * @param string $Text
+     * @param array $Format
+     */
+    public function drawArrowLabel($X1, $Y1, $Text, array $Format = [])
+    {
+        $FillR = isset($Format["FillR"]) ? $Format["FillR"] : 0;
+        $FillG = isset($Format["FillG"]) ? $Format["FillG"] : 0;
+        $FillB = isset($Format["FillB"]) ? $Format["FillB"] : 0;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $FillR;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $FillG;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $FillB;
+        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $Length = isset($Format["Length"]) ? $Format["Length"] : 50;
+        $Angle = isset($Format["Angle"]) ? ((int) $Format["Angle"]) : 315;
+        $Size = isset($Format["Size"]) ? $Format["Size"] : 10;
+        $Position = isset($Format["Position"]) ? $Format["Position"] : POSITION_TOP;
+        $RoundPos = isset($Format["RoundPos"]) ? $Format["RoundPos"] : false;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+
+        $Angle = $Angle % 360;
+
+        $X2 = sin(($Angle + 180) * PI / 180) * $Length + $X1;
+        $Y2 = cos(($Angle + 180) * PI / 180) * $Length + $Y1;
+
+        if ($RoundPos && $Angle > 0 && $Angle < 180) {
+            $Y2 = ceil($Y2);
+        }
+        if ($RoundPos && $Angle > 180) {
+            $Y2 = floor($Y2);
+        }
+
+        $this->drawArrow($X2, $Y2, $X1, $Y1, $Format);
+
+        $Size = imagettfbbox($FontSize, 0, $FontName, $Text);
+        $TxtWidth = max(abs($Size[2] - $Size[0]), abs($Size[0] - $Size[6]));
+
+        if ($Angle > 0 && $Angle < 180) {
+            $this->drawLine(
+                $X2,
+                $Y2,
+                $X2 - $TxtWidth,
+                $Y2,
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha, "Ticks" => $Ticks]
+            );
+            if ($Position == POSITION_TOP) {
+                $this->drawText(
+                    $X2,
+                    $Y2 - 2,
+                    $Text,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $Alpha,
+                        "Align" => TEXT_ALIGN_BOTTOMRIGHT
+                    ]
+                );
+            } else {
+                $this->drawText(
+                    $X2,
+                    $Y2 + 4,
+                    $Text,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $Alpha,
+                        "Align" => TEXT_ALIGN_TOPRIGHT
+                    ]
+                );
+            }
+        } else {
+            $this->drawLine(
+                $X2,
+                $Y2,
+                $X2 + $TxtWidth,
+                $Y2,
+                ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha, "Ticks" => $Ticks]
+            );
+            if ($Position == POSITION_TOP) {
+                $this->drawText(
+                    $X2,
+                    $Y2 - 2,
+                    $Text,
+                    ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $Alpha]
+                );
+            } else {
+                $this->drawText(
+                    $X2,
+                    $Y2 + 4,
+                    $Text,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $Alpha,
+                        "Align" => TEXT_ALIGN_TOPLEFT
+                    ]
+                );
+            }
+        }
+    }
+
+    /**
+     * Draw a progress bar filled with specified %
+     * @param int $X
+     * @param int $Y
+     * @param int|float $Percent
+     * @param array $Format
+     */
+    public function drawProgress($X, $Y, $Percent, array $Format = [])
+    {
+        if ($Percent > 100) {
+            $Percent = 100;
+        }
+        if ($Percent < 0) {
+            $Percent = 0;
+        }
+
+        $Width = isset($Format["Width"]) ? $Format["Width"] : 200;
+        $Height = isset($Format["Height"]) ? $Format["Height"] : 20;
+        $Orientation = isset($Format["Orientation"]) ? $Format["Orientation"] : ORIENTATION_HORIZONTAL;
+        $ShowLabel = isset($Format["ShowLabel"]) ? $Format["ShowLabel"] : false;
+        $LabelPos = isset($Format["LabelPos"]) ? $Format["LabelPos"] : LABEL_POS_INSIDE;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 10;
+        $R = isset($Format["R"]) ? $Format["R"] : 130;
+        $G = isset($Format["G"]) ? $Format["G"] : 130;
+        $B = isset($Format["B"]) ? $Format["B"] : 130;
+        $RFade = isset($Format["RFade"]) ? $Format["RFade"] : -1;
+        $GFade = isset($Format["GFade"]) ? $Format["GFade"] : -1;
+        $BFade = isset($Format["BFade"]) ? $Format["BFade"] : -1;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 0;
+        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 0;
+        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 0;
+        $BoxBackR = isset($Format["BoxBackR"]) ? $Format["BoxBackR"] : 255;
+        $BoxBackG = isset($Format["BoxBackG"]) ? $Format["BoxBackG"] : 255;
+        $BoxBackB = isset($Format["BoxBackB"]) ? $Format["BoxBackB"] : 255;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : null;
+        $NoAngle = isset($Format["NoAngle"]) ? $Format["NoAngle"] : false;
+
+        if ($RFade != -1 && $GFade != -1 && $BFade != -1) {
+            $RFade = (($RFade - $R) / 100) * $Percent + $R;
+            $GFade = (($GFade - $G) / 100) * $Percent + $G;
+            $BFade = (($BFade - $B) / 100) * $Percent + $B;
+        }
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+        if ($BoxSurrounding != null) {
+            $BoxBorderR = $BoxBackR + $Surrounding;
+            $BoxBorderG = $BoxBackG + $Surrounding;
+            $BoxBorderB = $BoxBackB + $Surrounding;
+        }
+
+        if ($Orientation == ORIENTATION_VERTICAL) {
+            $InnerHeight = (($Height - 2) / 100) * $Percent;
+            $this->drawFilledRectangle(
+                $X,
+                $Y,
+                $X + $Width,
+                $Y - $Height,
+                [
+                    "R" => $BoxBackR,
+                    "G" => $BoxBackG,
+                    "B" => $BoxBackB,
+                    "BorderR" => $BoxBorderR,
+                    "BorderG" => $BoxBorderG,
+                    "BorderB" => $BoxBorderB,
+                    "NoAngle" => $NoAngle
+                ]
+            );
+
+            $RestoreShadow = $this->Shadow;
+            $this->Shadow = false;
+            if ($RFade != -1 && $GFade != -1 && $BFade != -1) {
+                $GradientOptions = [
+                    "StartR" => $RFade,
+                    "StartG" => $GFade,
+                    "StartB" => $BFade,
+                    "EndR" => $R,
+                    "EndG" => $G,
+                    "EndB" => $B
+                ];
+                $this->drawGradientArea(
+                    $X + 1,
+                    $Y - 1,
+                    $X + $Width - 1,
+                    $Y - $InnerHeight,
+                    DIRECTION_VERTICAL,
+                    $GradientOptions
+                );
+
+                if ($Surrounding) {
+                    $this->drawRectangle(
+                        $X + 1,
+                        $Y - 1,
+                        $X + $Width - 1,
+                        $Y - $InnerHeight,
+                        ["R" => 255, "G" => 255, "B" => 255, "Alpha" => $Surrounding]
+                    );
+                }
+            } else {
+                $this->drawFilledRectangle(
+                    $X + 1,
+                    $Y - 1,
+                    $X + $Width - 1,
+                    $Y - $InnerHeight,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "BorderR" => $BorderR,
+                        "BorderG" => $BorderG,
+                        "BorderB" => $BorderB
+                    ]
+                );
+            }
+            $this->Shadow = $RestoreShadow;
+
+            if ($ShowLabel && $LabelPos == LABEL_POS_BOTTOM) {
+                $this->drawText(
+                    $X + ($Width / 2),
+                    $Y + $Margin,
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_TOP) {
+                $this->drawText(
+                    $X + ($Width / 2),
+                    $Y - $Height - $Margin,
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_INSIDE) {
+                $this->drawText(
+                    $X + ($Width / 2),
+                    $Y - $InnerHeight - $Margin,
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLELEFT, "Angle" => 90]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_CENTER) {
+                $this->drawText(
+                    $X + ($Width / 2),
+                    $Y - ($Height / 2),
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLEMIDDLE, "Angle" => 90]
+                );
+            }
+        } else {
+            if ($Percent == 100) {
+                $InnerWidth = $Width - 1;
+            } else {
+                $InnerWidth = (($Width - 2) / 100) * $Percent;
+            }
+            $this->drawFilledRectangle(
+                $X,
+                $Y,
+                $X + $Width,
+                $Y + $Height,
+                [
+                    "R" => $BoxBackR,
+                    "G" => $BoxBackG,
+                    "B" => $BoxBackB,
+                    "BorderR" => $BoxBorderR,
+                    "BorderG" => $BoxBorderG,
+                    "BorderB" => $BoxBorderB,
+                    "NoAngle" => $NoAngle
+                ]
+            );
+
+            $RestoreShadow = $this->Shadow;
+            $this->Shadow = false;
+            if ($RFade != -1 && $GFade != -1 && $BFade != -1) {
+                $GradientOptions = [
+                    "StartR" => $R,
+                    "StartG" => $G,
+                    "StartB" => $B,
+                    "EndR" => $RFade,
+                    "EndG" => $GFade,
+                    "EndB" => $BFade
+                ];
+                $this->drawGradientArea(
+                    $X + 1,
+                    $Y + 1,
+                    $X + $InnerWidth,
+                    $Y + $Height - 1,
+                    DIRECTION_HORIZONTAL,
+                    $GradientOptions
+                );
+
+                if ($Surrounding) {
+                    $this->drawRectangle(
+                        $X + 1,
+                        $Y + 1,
+                        $X + $InnerWidth,
+                        $Y + $Height - 1,
+                        ["R" => 255, "G" => 255, "B" => 255, "Alpha" => $Surrounding]
+                    );
+                }
+            } else {
+                $this->drawFilledRectangle(
+                    $X + 1,
+                    $Y + 1,
+                    $X + $InnerWidth,
+                    $Y + $Height - 1,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "BorderR" => $BorderR, "BorderG" => $BorderG, "BorderB" => $BorderB
+                    ]
+                );
+            }
+            $this->Shadow = $RestoreShadow;
+
+            if ($ShowLabel && $LabelPos == LABEL_POS_LEFT) {
+                $this->drawText(
+                    $X - $Margin,
+                    $Y + ($Height / 2),
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLERIGHT]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_RIGHT) {
+                $this->drawText(
+                    $X + $Width + $Margin,
+                    $Y + ($Height / 2),
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_CENTER) {
+                $this->drawText(
+                    $X + ($Width / 2),
+                    $Y + ($Height / 2),
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLEMIDDLE]
+                );
+            }
+            if ($ShowLabel && $LabelPos == LABEL_POS_INSIDE) {
+                $this->drawText(
+                    $X + $InnerWidth + $Margin,
+                    $Y + ($Height / 2),
+                    $Percent . "%",
+                    ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                );
+            }
+        }
+    }
+
+    /**
+     * Draw the legend of the active series
+     * @param int $X
+     * @param int $Y
+     * @param array $Format
+     */
+    public function drawLegend($X, $Y, array $Format = [])
+    {
+        $Family = isset($Format["Family"]) ? $Format["Family"] : LEGEND_FAMILY_BOX;
+        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
+        $FontR = isset($Format["FontR"]) ? $Format["FontR"] : $this->FontColorR;
+        $FontG = isset($Format["FontG"]) ? $Format["FontG"] : $this->FontColorG;
+        $FontB = isset($Format["FontB"]) ? $Format["FontB"] : $this->FontColorB;
+        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 5;
+        $BoxHeight = isset($Format["BoxHeight"]) ? $Format["BoxHeight"] : 5;
+        $IconAreaWidth = isset($Format["IconAreaWidth"]) ? $Format["IconAreaWidth"] : $BoxWidth;
+        $IconAreaHeight = isset($Format["IconAreaHeight"]) ? $Format["IconAreaHeight"] : $BoxHeight;
+        $XSpacing = isset($Format["XSpacing"]) ? $Format["XSpacing"] : 5;
+        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
+        $R = isset($Format["R"]) ? $Format["R"] : 200;
+        $G = isset($Format["G"]) ? $Format["G"] : 200;
+        $B = isset($Format["B"]) ? $Format["B"] : 200;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Style = isset($Format["Style"]) ? $Format["Style"] : LEGEND_ROUND;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        $Data = $this->DataSet->getData();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true
+                && $SerieName != $Data["Abscissa"]
+                && isset($Serie["Picture"])
+            ) {
+                list($PicWidth, $PicHeight) = $this->getPicInfo($Serie["Picture"]);
+                if ($IconAreaWidth < $PicWidth) {
+                    $IconAreaWidth = $PicWidth;
+                }
+                if ($IconAreaHeight < $PicHeight) {
+                    $IconAreaHeight = $PicHeight;
+                }
+            }
+        }
+
+        $YStep = max($this->FontSize, $IconAreaHeight) + 5;
+        $XStep = $IconAreaWidth + 5;
+        $XStep = $XSpacing;
+
+        $Boundaries = [];
+        $Boundaries["L"] = $X;
+        $Boundaries["T"] = $Y;
+        $Boundaries["R"] = 0;
+        $Boundaries["B"] = 0;
+        $vY = $Y;
+        $vX = $X;
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                if ($Mode == LEGEND_VERTICAL) {
+                    $BoxArray = $this->getTextBox(
+                        $vX + $IconAreaWidth + 4,
+                        $vY + $IconAreaHeight / 2,
+                        $FontName,
+                        $FontSize,
+                        0,
+                        $Serie["Description"]
+                    );
+
+                    if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                        $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                    }
+                    if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                        $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                    }
+                    if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                        $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                    }
+
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    $vY = $vY + max($this->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->getTextBox(
+                            $vX + $IconAreaWidth + 6,
+                            $Y + $IconAreaHeight / 2 + (($this->FontSize + 3) * $Key),
+                            $FontName,
+                            $FontSize,
+                            0,
+                            $Value
+                        );
+
+                        if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
+                            $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
+                        }
+                        if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
+                            $Boundaries["R"] = $BoxArray[1]["X"] + 2;
+                        }
+                        if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
+                            $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
+                        }
+
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+
+                    $vX = max($Width) + $XStep;
+                }
+            }
+        }
+        $vY = $vY - $YStep;
+        $vX = $vX - $XStep;
+
+        $TopOffset = $Y - $Boundaries["T"];
+        if ($Boundaries["B"] - ($vY + $IconAreaHeight) < $TopOffset) {
+            $Boundaries["B"] = $vY + $IconAreaHeight + $TopOffset;
+        }
+
+        if ($Style == LEGEND_ROUND) {
+            $this->drawRoundedFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        } elseif ($Style == LEGEND_BOX) {
+            $this->drawFilledRectangle(
+                $Boundaries["L"] - $Margin,
+                $Boundaries["T"] - $Margin,
+                $Boundaries["R"] + $Margin,
+                $Boundaries["B"] + $Margin,
+                [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ]
+            );
+        }
+
+        $RestoreShadow = $this->Shadow;
+        $this->Shadow = false;
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Ticks = $Serie["Ticks"];
+                $Weight = $Serie["Weight"];
+
+                if (isset($Serie["Picture"])) {
+                    $Picture = $Serie["Picture"];
+                    list($PicWidth, $PicHeight) = $this->getPicInfo($Picture);
+                    $PicX = $X + $IconAreaWidth / 2;
+                    $PicY = $Y + $IconAreaHeight / 2;
+
+                    $this->drawFromPNG($PicX - $PicWidth / 2, $PicY - $PicHeight / 2, $Picture);
+                } else {
+                    if ($Family == LEGEND_FAMILY_BOX) {
+                        $XOffset = 0;
+                        if ($BoxWidth != $IconAreaWidth) {
+                            $XOffset = floor(($IconAreaWidth - $BoxWidth) / 2);
+                        }
+                        $YOffset = 0;
+                        if ($BoxHeight != $IconAreaHeight) {
+                            $YOffset = floor(($IconAreaHeight - $BoxHeight) / 2);
+                        }
+
+                        $this->drawFilledRectangle(
+                            $X + 1 + $XOffset,
+                            $Y + 1 + $YOffset,
+                            $X + $BoxWidth + $XOffset + 1,
+                            $Y + $BoxHeight + 1 + $YOffset,
+                            ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
+                        );
+                        $this->drawFilledRectangle(
+                            $X + $XOffset,
+                            $Y + $YOffset,
+                            $X + $BoxWidth + $XOffset,
+                            $Y + $BoxHeight + $YOffset,
+                            ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
+                        );
+                    } elseif ($Family == LEGEND_FAMILY_CIRCLE) {
+                        $this->drawFilledCircle(
+                            $X + 1 + $IconAreaWidth / 2,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            min($IconAreaHeight / 2, $IconAreaWidth / 2),
+                            ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
+                        );
+                        $this->drawFilledCircle(
+                            $X + $IconAreaWidth / 2,
+                            $Y + $IconAreaHeight / 2,
+                            min($IconAreaHeight / 2, $IconAreaWidth / 2),
+                            ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
+                        );
+                    } elseif ($Family == LEGEND_FAMILY_LINE) {
+                        $this->drawLine(
+                            $X + 1,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            $X + 1 + $IconAreaWidth,
+                            $Y + 1 + $IconAreaHeight / 2,
+                            ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20, "Ticks" => $Ticks, "Weight" => $Weight]
+                        );
+                        $this->drawLine(
+                            $X,
+                            $Y + $IconAreaHeight / 2,
+                            $X + $IconAreaWidth,
+                            $Y + $IconAreaHeight / 2,
+                            ["R" => $R, "G" => $G, "B" => $B, "Ticks" => $Ticks, "Weight" => $Weight]
+                        );
+                    }
+                }
+
+                if ($Mode == LEGEND_VERTICAL) {
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    foreach ($Lines as $Key => $Value) {
+                        $this->drawText(
+                            $X + $IconAreaWidth + 4,
+                            $Y + $IconAreaHeight / 2 + (($this->FontSize + 3) * $Key),
+                            $Value,
+                            [
+                                "R" => $FontR,
+                                "G" => $FontG,
+                                "B" => $FontB,
+                                "Align" => TEXT_ALIGN_MIDDLELEFT,
+                                "FontSize" => $FontSize,
+                                "FontName" => $FontName
+                            ]
+                        );
+                    }
+                    $Y = $Y + max($this->FontSize * count($Lines), $IconAreaHeight) + 5;
+                } elseif ($Mode == LEGEND_HORIZONTAL) {
+                    $Lines = preg_split("/\n/", $Serie["Description"]);
+                    $Width = [];
+                    foreach ($Lines as $Key => $Value) {
+                        $BoxArray = $this->drawText(
+                            $X + $IconAreaWidth + 4,
+                            $Y + $IconAreaHeight / 2 + (($this->FontSize + 3) * $Key),
+                            $Value,
+                            [
+                                "R" => $FontR,
+                                "G" => $FontG,
+                                "B" => $FontB,
+                                "Align" => TEXT_ALIGN_MIDDLELEFT,
+                                "FontSize" => $FontSize,
+                                "FontName" => $FontName
+                            ]
+                        );
+                        $Width[] = $BoxArray[1]["X"];
+                    }
+                    $X = max($Width) + 2 + $XStep;
+                }
+            }
+        }
+
+
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * @param array $Format
+     * @throws Exception
+     */
+    public function drawScale(array $Format = [])
+    {
+        $FloatingOffset = 0;
+        $Pos = isset($Format["Pos"]) ? $Format["Pos"] : SCALE_POS_LEFTRIGHT;
+        $Floating = isset($Format["Floating"]) ? $Format["Floating"] : false;
+        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : SCALE_MODE_FLOATING;
+        $RemoveXAxis = isset($Format["RemoveXAxis"]) ? $Format["RemoveXAxis"] : false;
+        $RemoveYAxis = isset($Format["RemoveYAxis"]) ? $Format["RemoveYAxis"] : false;
+        $RemoveYAxiValues = isset($Format["RemoveYAxisValues"]) ? $Format["RemoveYAxisValues"] : false;
+        $MinDivHeight = isset($Format["MinDivHeight"]) ? $Format["MinDivHeight"] : 20;
+        $Factors = isset($Format["Factors"]) ? $Format["Factors"] : [1, 2, 5];
+        $ManualScale = isset($Format["ManualScale"])
+            ? $Format["ManualScale"] : ["0" => ["Min" => -100, "Max" => 100]]
+        ;
+        $XMargin = isset($Format["XMargin"]) ? $Format["XMargin"] : AUTO;
+        $YMargin = isset($Format["YMargin"]) ? $Format["YMargin"] : 0;
+        $ScaleSpacing = isset($Format["ScaleSpacing"]) ? $Format["ScaleSpacing"] : 15;
+        $InnerTickWidth = isset($Format["InnerTickWidth"]) ? $Format["InnerTickWidth"] : 2;
+        $OuterTickWidth = isset($Format["OuterTickWidth"]) ? $Format["OuterTickWidth"] : 2;
+        $DrawXLines = isset($Format["DrawXLines"]) ? $Format["DrawXLines"] : true;
+        $DrawYLines = isset($Format["DrawYLines"]) ? $Format["DrawYLines"] : ALL;
+        $GridTicks = isset($Format["GridTicks"]) ? $Format["GridTicks"] : 4;
+        $GridR = isset($Format["GridR"]) ? $Format["GridR"] : 255;
+        $GridG = isset($Format["GridG"]) ? $Format["GridG"] : 255;
+        $GridB = isset($Format["GridB"]) ? $Format["GridB"] : 255;
+        $GridAlpha = isset($Format["GridAlpha"]) ? $Format["GridAlpha"] : 40;
+        $AxisRo = isset($Format["AxisR"]) ? $Format["AxisR"] : 0;
+        $AxisGo = isset($Format["AxisG"]) ? $Format["AxisG"] : 0;
+        $AxisBo = isset($Format["AxisB"]) ? $Format["AxisB"] : 0;
+        $AxisAlpha = isset($Format["AxisAlpha"]) ? $Format["AxisAlpha"] : 100;
+        $TickRo = isset($Format["TickR"]) ? $Format["TickR"] : 0;
+        $TickGo = isset($Format["TickG"]) ? $Format["TickG"] : 0;
+        $TickBo = isset($Format["TickB"]) ? $Format["TickB"] : 0;
+        $TickAlpha = isset($Format["TickAlpha"]) ? $Format["TickAlpha"] : 100;
+        $DrawSubTicks = isset($Format["DrawSubTicks"]) ? $Format["DrawSubTicks"] : false;
+        $InnerSubTickWidth = isset($Format["InnerSubTickWidth"]) ? $Format["InnerSubTickWidth"] : 0;
+        $OuterSubTickWidth = isset($Format["OuterSubTickWidth"]) ? $Format["OuterSubTickWidth"] : 2;
+        $SubTickR = isset($Format["SubTickR"]) ? $Format["SubTickR"] : 255;
+        $SubTickG = isset($Format["SubTickG"]) ? $Format["SubTickG"] : 0;
+        $SubTickB = isset($Format["SubTickB"]) ? $Format["SubTickB"] : 0;
+        $SubTickAlpha = isset($Format["SubTickAlpha"]) ? $Format["SubTickAlpha"] : 100;
+        $AutoAxisLabels = isset($Format["AutoAxisLabels"]) ? $Format["AutoAxisLabels"] : true;
+        $XReleasePercent = isset($Format["XReleasePercent"]) ? $Format["XReleasePercent"] : 1;
+        $DrawArrows = isset($Format["DrawArrows"]) ? $Format["DrawArrows"] : false;
+        $ArrowSize = isset($Format["ArrowSize"]) ? $Format["ArrowSize"] : 8;
+        $CycleBackground = isset($Format["CycleBackground"]) ? $Format["CycleBackground"] : false;
+        $BackgroundR1 = isset($Format["BackgroundR1"]) ? $Format["BackgroundR1"] : 255;
+        $BackgroundG1 = isset($Format["BackgroundG1"]) ? $Format["BackgroundG1"] : 255;
+        $BackgroundB1 = isset($Format["BackgroundB1"]) ? $Format["BackgroundB1"] : 255;
+        $BackgroundAlpha1 = isset($Format["BackgroundAlpha1"]) ? $Format["BackgroundAlpha1"] : 20;
+        $BackgroundR2 = isset($Format["BackgroundR2"]) ? $Format["BackgroundR2"] : 230;
+        $BackgroundG2 = isset($Format["BackgroundG2"]) ? $Format["BackgroundG2"] : 230;
+        $BackgroundB2 = isset($Format["BackgroundB2"]) ? $Format["BackgroundB2"] : 230;
+        $BackgroundAlpha2 = isset($Format["BackgroundAlpha2"]) ? $Format["BackgroundAlpha2"] : 20;
+        $LabelingMethod = isset($Format["LabelingMethod"]) ? $Format["LabelingMethod"] : LABELING_ALL;
+        $LabelSkip = isset($Format["LabelSkip"]) ? $Format["LabelSkip"] : 0;
+        $LabelRotation = isset($Format["LabelRotation"]) ? $Format["LabelRotation"] : 0;
+        $RemoveSkippedAxis = isset($Format["RemoveSkippedAxis"]) ? $Format["RemoveSkippedAxis"] : false;
+        $SkippedAxisTicks = isset($Format["SkippedAxisTicks"]) ? $Format["SkippedAxisTicks"] : $GridTicks + 2;
+        $SkippedAxisR = isset($Format["SkippedAxisR"]) ? $Format["SkippedAxisR"] : $GridR;
+        $SkippedAxisG = isset($Format["SkippedAxisG"]) ? $Format["SkippedAxisG"] : $GridG;
+        $SkippedAxisB = isset($Format["SkippedAxisB"]) ? $Format["SkippedAxisB"] : $GridB;
+        $SkippedAxisAlpha = isset($Format["SkippedAxisAlpha"]) ? $Format["SkippedAxisAlpha"] : $GridAlpha - 30;
+        $SkippedTickR = isset($Format["SkippedTickR"]) ? $Format["SkippedTickR"] : $TickRo;
+        $SkippedTickG = isset($Format["SkippedTickG"]) ? $Format["SkippedTickG"] : $TickGo;
+        $SkippedTickB = isset($Format["SkippedTicksB"]) ? $Format["SkippedTickB"] : $TickBo;
+        $SkippedTickAlpha = isset($Format["SkippedTickAlpha"]) ? $Format["SkippedTickAlpha"] : $TickAlpha - 80;
+        $SkippedInnerTickWidth = isset($Format["SkippedInnerTickWidth"]) ? $Format["SkippedInnerTickWidth"] : 0;
+        $SkippedOuterTickWidth = isset($Format["SkippedOuterTickWidth"]) ? $Format["SkippedOuterTickWidth"] : 2;
+
+        /* Floating scale require X & Y margins to be set manually */
+        if ($Floating && ($XMargin == AUTO || $YMargin == 0)) {
+            $Floating = false;
+        }
+
+        /* Skip a NOTICE event in case of an empty array */
+        if ($DrawYLines == NONE || $DrawYLines == false) {
+            $DrawYLines = ["zarma" => "31"];
+        }
+
+        /* Define the color for the skipped elements */
+        $SkippedAxisColor = [
+            "R" => $SkippedAxisR,
+            "G" => $SkippedAxisG,
+            "B" => $SkippedAxisB,
+            "Alpha" => $SkippedAxisAlpha,
+            "Ticks" => $SkippedAxisTicks
+        ];
+        $SkippedTickColor = [
+            "R" => $SkippedTickR,
+            "G" => $SkippedTickG,
+            "B" => $SkippedTickB,
+            "Alpha" => $SkippedTickAlpha
+        ];
+
+        $Data = $this->DataSet->getData();
+        $Abscissa = null;
+        if (isset($Data["Abscissa"])) {
+            $Abscissa = $Data["Abscissa"];
+        }
+
+        /* Unset the abscissa axis, needed if we display multiple charts on the same picture */
+        if ($Abscissa != null) {
+            foreach ($Data["Axis"] as $AxisID => $Parameters) {
+                if ($Parameters["Identity"] == AXIS_X) {
+                    unset($Data["Axis"][$AxisID]);
+                }
+            }
+        }
+
+        /* Build the scale settings */
+        $GotXAxis = false;
+        foreach ($Data["Axis"] as $AxisID => $AxisParameter) {
+            if ($AxisParameter["Identity"] == AXIS_X) {
+                $GotXAxis = true;
+            }
+
+            if ($Pos == SCALE_POS_LEFTRIGHT && $AxisParameter["Identity"] == AXIS_Y) {
+                $Height = $this->GraphAreaY2 - $this->GraphAreaY1 - $YMargin * 2;
+            } elseif ($Pos == SCALE_POS_LEFTRIGHT && $AxisParameter["Identity"] == AXIS_X) {
+                $Height = $this->GraphAreaX2 - $this->GraphAreaX1;
+            } elseif ($Pos == SCALE_POS_TOPBOTTOM && $AxisParameter["Identity"] == AXIS_Y) {
+                $Height = $this->GraphAreaX2 - $this->GraphAreaX1 - $YMargin * 2;
+                ;
+            } else {
+                $Height = $this->GraphAreaY2 - $this->GraphAreaY1;
+            }
+
+            $AxisMin = ABSOLUTE_MAX;
+            $AxisMax = OUT_OF_SIGHT;
+            if ($Mode == SCALE_MODE_FLOATING || $Mode == SCALE_MODE_START0) {
+                foreach ($Data["Series"] as $SerieID => $SerieParameter) {
+                    if ($SerieParameter["Axis"] == $AxisID
+                        && $Data["Series"][$SerieID]["isDrawable"]
+                        && $Data["Abscissa"] != $SerieID
+                    ) {
+                        $AxisMax = max($AxisMax, $Data["Series"][$SerieID]["Max"]);
+                        $AxisMin = min($AxisMin, $Data["Series"][$SerieID]["Min"]);
+                    }
+                }
+                $AutoMargin = (($AxisMax - $AxisMin) / 100) * $XReleasePercent;
+
+                $Data["Axis"][$AxisID]["Min"] = $AxisMin - $AutoMargin;
+                $Data["Axis"][$AxisID]["Max"] = $AxisMax + $AutoMargin;
+                if ($Mode == SCALE_MODE_START0) {
+                    $Data["Axis"][$AxisID]["Min"] = 0;
+                }
+            } elseif ($Mode == SCALE_MODE_MANUAL) {
+                if (isset($ManualScale[$AxisID]["Min"]) && isset($ManualScale[$AxisID]["Max"])) {
+                    $Data["Axis"][$AxisID]["Min"] = $ManualScale[$AxisID]["Min"];
+                    $Data["Axis"][$AxisID]["Max"] = $ManualScale[$AxisID]["Max"];
+                } else {
+                    throw new Exception("Manual scale boundaries not set.");
+                }
+            } elseif ($Mode == SCALE_MODE_ADDALL || $Mode == SCALE_MODE_ADDALL_START0) {
+                $Series = [];
+                foreach ($Data["Series"] as $SerieID => $SerieParameter) {
+                    if ($SerieParameter["Axis"] == $AxisID
+                        && $SerieParameter["isDrawable"]
+                        && $Data["Abscissa"] != $SerieID
+                    ) {
+                        $Series[$SerieID] = count($Data["Series"][$SerieID]["Data"]);
+                    }
+                }
+
+                for ($ID = 0; $ID <= max($Series) - 1; $ID++) {
+                    $PointMin = 0;
+                    $PointMax = 0;
+                    foreach ($Series as $SerieID => $ValuesCount) {
+                        if (isset($Data["Series"][$SerieID]["Data"][$ID])
+                            && $Data["Series"][$SerieID]["Data"][$ID] != null
+                        ) {
+                            $Value = $Data["Series"][$SerieID]["Data"][$ID];
+                            if ($Value > 0) {
+                                $PointMax = $PointMax + $Value;
+                            } else {
+                                $PointMin = $PointMin + $Value;
+                            }
+                        }
+                    }
+                    $AxisMax = max($AxisMax, $PointMax);
+                    $AxisMin = min($AxisMin, $PointMin);
+                }
+                $AutoMargin = (($AxisMax - $AxisMin) / 100) * $XReleasePercent;
+                $Data["Axis"][$AxisID]["Min"] = $AxisMin - $AutoMargin;
+                $Data["Axis"][$AxisID]["Max"] = $AxisMax + $AutoMargin;
+            }
+            $MaxDivs = floor($Height / $MinDivHeight);
+
+            if ($Mode == SCALE_MODE_ADDALL_START0) {
+                $Data["Axis"][$AxisID]["Min"] = 0;
+            }
+
+            $Scale = $this->computeScale(
+                $Data["Axis"][$AxisID]["Min"],
+                $Data["Axis"][$AxisID]["Max"],
+                $MaxDivs,
+                $Factors,
+                $AxisID
+            );
+
+            $Data["Axis"][$AxisID]["Margin"] = $AxisParameter["Identity"] == AXIS_X ? $XMargin : $YMargin;
+            $Data["Axis"][$AxisID]["ScaleMin"] = $Scale["XMin"];
+            $Data["Axis"][$AxisID]["ScaleMax"] = $Scale["XMax"];
+            $Data["Axis"][$AxisID]["Rows"] = $Scale["Rows"];
+            $Data["Axis"][$AxisID]["RowHeight"] = $Scale["RowHeight"];
+
+            if (isset($Scale["Format"])) {
+                $Data["Axis"][$AxisID]["Format"] = $Scale["Format"];
+            }
+            if (!isset($Data["Axis"][$AxisID]["Display"])) {
+                $Data["Axis"][$AxisID]["Display"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Format"])) {
+                $Data["Axis"][$AxisID]["Format"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Unit"])) {
+                $Data["Axis"][$AxisID]["Unit"] = null;
+            }
+        }
+
+        /* Still no X axis */
+        if ($GotXAxis == false) {
+            if ($Abscissa != null) {
+                $Points = count($Data["Series"][$Abscissa]["Data"]);
+                $AxisName = null;
+                if ($AutoAxisLabels) {
+                    $AxisName = isset($Data["Series"][$Abscissa]["Description"])
+                        ? $Data["Series"][$Abscissa]["Description"] : null
+                    ;
+                }
+            } else {
+                $Points = 0;
+                $AxisName = isset($Data["XAxisName"]) ? $Data["XAxisName"] : null;
+                foreach ($Data["Series"] as $SerieID => $SerieParameter) {
+                    if ($SerieParameter["isDrawable"]) {
+                        $Points = max($Points, count($SerieParameter["Data"]));
+                    }
+                }
+            }
+
+            $AxisID = count($Data["Axis"]);
+            $Data["Axis"][$AxisID]["Identity"] = AXIS_X;
+            if ($Pos == SCALE_POS_LEFTRIGHT) {
+                $Data["Axis"][$AxisID]["Position"] = AXIS_POSITION_BOTTOM;
+            } else {
+                $Data["Axis"][$AxisID]["Position"] = AXIS_POSITION_LEFT;
+            }
+            if (isset($Data["AbscissaName"])) {
+                $Data["Axis"][$AxisID]["Name"] = $Data["AbscissaName"];
+            }
+            if ($XMargin == AUTO) {
+                if ($Pos == SCALE_POS_LEFTRIGHT) {
+                    $Height = $this->GraphAreaX2 - $this->GraphAreaX1;
+                } else {
+                    $Height = $this->GraphAreaY2 - $this->GraphAreaY1;
+                }
+
+                if ($Points == 0 || $Points == 1) {
+                    $Data["Axis"][$AxisID]["Margin"] = $Height / 2;
+                } else {
+                    $Data["Axis"][$AxisID]["Margin"] = ($Height / $Points) / 2;
+                }
+            } else {
+                $Data["Axis"][$AxisID]["Margin"] = $XMargin;
+            }
+            $Data["Axis"][$AxisID]["Rows"] = $Points - 1;
+            if (!isset($Data["Axis"][$AxisID]["Display"])) {
+                $Data["Axis"][$AxisID]["Display"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Format"])) {
+                $Data["Axis"][$AxisID]["Format"] = null;
+            }
+            if (!isset($Data["Axis"][$AxisID]["Unit"])) {
+                $Data["Axis"][$AxisID]["Unit"] = null;
+            }
+        }
+
+        /* Do we need to reverse the abscissa position? */
+        if ($Pos != SCALE_POS_LEFTRIGHT) {
+            $Data["AbsicssaPosition"] = AXIS_POSITION_RIGHT;
+            if ($Data["AbsicssaPosition"] == AXIS_POSITION_BOTTOM) {
+                $Data["AbsicssaPosition"] = AXIS_POSITION_LEFT;
+            }
+        }
+        $Data["Axis"][$AxisID]["Position"] = $Data["AbsicssaPosition"];
+
+        $this->DataSet->saveOrientation($Pos);
+        $this->DataSet->saveAxisConfig($Data["Axis"]);
+        $this->DataSet->saveYMargin($YMargin);
+
+        $FontColorRo = $this->FontColorR;
+        $FontColorGo = $this->FontColorG;
+        $FontColorBo = $this->FontColorB;
+
+        $AxisPos["L"] = $this->GraphAreaX1;
+        $AxisPos["R"] = $this->GraphAreaX2;
+        $AxisPos["T"] = $this->GraphAreaY1;
+        $AxisPos["B"] = $this->GraphAreaY2;
+        foreach ($Data["Axis"] as $AxisID => $Parameters) {
+            if (isset($Parameters["Color"])) {
+                $AxisR = $Parameters["Color"]["R"];
+                $AxisG = $Parameters["Color"]["G"];
+                $AxisB = $Parameters["Color"]["B"];
+                $TickR = $Parameters["Color"]["R"];
+                $TickG = $Parameters["Color"]["G"];
+                $TickB = $Parameters["Color"]["B"];
+                $this->setFontProperties(
+                    [
+                        "R" => $Parameters["Color"]["R"],
+                        "G" => $Parameters["Color"]["G"],
+                        "B" => $Parameters["Color"]["B"]
+                    ]
+                );
+            } else {
+                $AxisR = $AxisRo;
+                $AxisG = $AxisGo;
+                $AxisB = $AxisBo;
+                $TickR = $TickRo;
+                $TickG = $TickGo;
+                $TickB = $TickBo;
+                $this->setFontProperties(["R" => $FontColorRo, "G" => $FontColorGo, "B" => $FontColorBo]);
+            }
+
+            $LastValue = "w00t";
+            $ID = 1;
+            if ($Parameters["Identity"] == AXIS_X) {
+                if ($Pos == SCALE_POS_LEFTRIGHT) {
+                    if ($Parameters["Position"] == AXIS_POSITION_BOTTOM) {
+                        if ($LabelRotation == 0) {
+                            $LabelAlign = TEXT_ALIGN_TOPMIDDLE;
+                            $YLabelOffset = 2;
+                        }
+                        if ($LabelRotation > 0 && $LabelRotation < 190) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $YLabelOffset = 5;
+                        }
+                        if ($LabelRotation == 180) {
+                            $LabelAlign = TEXT_ALIGN_BOTTOMMIDDLE;
+                            $YLabelOffset = 5;
+                        }
+                        if ($LabelRotation > 180 && $LabelRotation < 360) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $YLabelOffset = 2;
+                        }
+
+                        if (!$RemoveXAxis) {
+                            if ($Floating) {
+                                $FloatingOffset = $YMargin;
+                                $this->drawLine(
+                                    $this->GraphAreaX1 + $Parameters["Margin"],
+                                    $AxisPos["B"],
+                                    $this->GraphAreaX2 - $Parameters["Margin"],
+                                    $AxisPos["B"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            } else {
+                                $FloatingOffset = 0;
+                                $this->drawLine(
+                                    $this->GraphAreaX1,
+                                    $AxisPos["B"],
+                                    $this->GraphAreaX2,
+                                    $AxisPos["B"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            }
+
+                            if ($DrawArrows) {
+                                $this->drawArrow(
+                                    $this->GraphAreaX2 - $Parameters["Margin"],
+                                    $AxisPos["B"],
+                                    $this->GraphAreaX2 + ($ArrowSize * 2),
+                                    $AxisPos["B"],
+                                    ["FillR" => $AxisR, "FillG" => $AxisG, "FillB" => $AxisB, "Size" => $ArrowSize]
+                                );
+                            }
+                        }
+
+                        $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Parameters["Margin"] * 2;
+
+                        if ($Parameters["Rows"] == 0) {
+                            $Step = $Width;
+                        } else {
+                            $Step = $Width / ($Parameters["Rows"]);
+                        }
+
+                        $MaxBottom = $AxisPos["B"];
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $XPos = $this->GraphAreaX1 + $Parameters["Margin"] + $Step * $i;
+                            $YPos = $AxisPos["B"];
+
+                            if ($Abscissa != null) {
+                                $Value = "";
+                                if (isset($Data["Series"][$Abscissa]["Data"][$i])) {
+                                    $Value = $this->scaleFormat(
+                                        $Data["Series"][$Abscissa]["Data"][$i],
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            } else {
+                                $Value = $i;
+                                if (isset($Parameters["ScaleMin"]) && isset($Parameters["RowHeight"])) {
+                                    $Value = $this->scaleFormat(
+                                        $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            }
+
+                            $ID++;
+                            $Skipped = true;
+                            if ($this->isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
+                                && !$RemoveXAxis
+                            ) {
+                                $Bounds = $this->drawText(
+                                    $XPos,
+                                    $YPos + $OuterTickWidth + $YLabelOffset,
+                                    $Value,
+                                    ["Angle" => $LabelRotation, "Align" => $LabelAlign]
+                                );
+                                $TxtBottom = $YPos + $OuterTickWidth + 2 + ($Bounds[0]["Y"] - $Bounds[2]["Y"]);
+                                $MaxBottom = max($MaxBottom, $TxtBottom);
+                                $LastValue = $Value;
+                                $Skipped = false;
+                            }
+
+                            if ($RemoveXAxis) {
+                                $Skipped = false;
+                            }
+
+                            if ($Skipped) {
+                                if ($DrawXLines && !$RemoveSkippedAxis) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $this->GraphAreaY1 + $FloatingOffset,
+                                        $XPos,
+                                        $this->GraphAreaY2 - $FloatingOffset,
+                                        $SkippedAxisColor
+                                    );
+                                }
+                                if (($SkippedInnerTickWidth != 0 || $SkippedOuterTickWidth != 0)
+                                    && !$RemoveXAxis
+                                    && !$RemoveSkippedAxis
+                                ) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $YPos - $SkippedInnerTickWidth,
+                                        $XPos,
+                                        $YPos + $SkippedOuterTickWidth,
+                                        $SkippedTickColor
+                                    );
+                                }
+                            } else {
+                                if ($DrawXLines
+                                    && ($XPos != $this->GraphAreaX1 && $XPos != $this->GraphAreaX2)
+                                ) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $this->GraphAreaY1 + $FloatingOffset,
+                                        $XPos,
+                                        $this->GraphAreaY2 - $FloatingOffset,
+                                        [
+                                            "R" => $GridR,
+                                            "G" => $GridG,
+                                            "B" => $GridB,
+                                            "Alpha" => $GridAlpha,
+                                            "Ticks" => $GridTicks
+                                        ]
+                                    );
+                                }
+                                if (($InnerTickWidth != 0 || $OuterTickWidth != 0) && !$RemoveXAxis) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $YPos - $InnerTickWidth,
+                                        $XPos,
+                                        $YPos + $OuterTickWidth,
+                                        ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                                    );
+                                }
+                            }
+                        }
+
+                        if (isset($Parameters["Name"]) && !$RemoveXAxis) {
+                            $YPos = $MaxBottom + 2;
+                            $XPos = $this->GraphAreaX1 + ($this->GraphAreaX2 - $this->GraphAreaX1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                            );
+                            $MaxBottom = $Bounds[0]["Y"];
+
+                            $this->DataSet->Data["GraphArea"]["Y2"] = $MaxBottom + $this->FontSize;
+                        }
+
+                        $AxisPos["B"] = $MaxBottom + $ScaleSpacing;
+                    } elseif ($Parameters["Position"] == AXIS_POSITION_TOP) {
+                        if ($LabelRotation == 0) {
+                            $LabelAlign = TEXT_ALIGN_BOTTOMMIDDLE;
+                            $YLabelOffset = 2;
+                        }
+                        if ($LabelRotation > 0 && $LabelRotation < 190) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $YLabelOffset = 2;
+                        }
+                        if ($LabelRotation == 180) {
+                            $LabelAlign = TEXT_ALIGN_TOPMIDDLE;
+                            $YLabelOffset = 5;
+                        }
+                        if ($LabelRotation > 180 && $LabelRotation < 360) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $YLabelOffset = 5;
+                        }
+
+                        if (!$RemoveXAxis) {
+                            if ($Floating) {
+                                $FloatingOffset = $YMargin;
+                                $this->drawLine(
+                                    $this->GraphAreaX1 + $Parameters["Margin"],
+                                    $AxisPos["T"],
+                                    $this->GraphAreaX2 - $Parameters["Margin"],
+                                    $AxisPos["T"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            } else {
+                                $FloatingOffset = 0;
+                                $this->drawLine(
+                                    $this->GraphAreaX1,
+                                    $AxisPos["T"],
+                                    $this->GraphAreaX2,
+                                    $AxisPos["T"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            }
+
+                            if ($DrawArrows) {
+                                $this->drawArrow(
+                                    $this->GraphAreaX2 - $Parameters["Margin"],
+                                    $AxisPos["T"],
+                                    $this->GraphAreaX2 + ($ArrowSize * 2),
+                                    $AxisPos["T"],
+                                    ["FillR" => $AxisR, "FillG" => $AxisG, "FillB" => $AxisB, "Size" => $ArrowSize]
+                                );
+                            }
+                        }
+
+                        $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Parameters["Margin"] * 2;
+
+                        if ($Parameters["Rows"] == 0) {
+                            $Step = $Width;
+                        } else {
+                            $Step = $Width / $Parameters["Rows"];
+                        }
+
+                        $MinTop = $AxisPos["T"];
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $XPos = $this->GraphAreaX1 + $Parameters["Margin"] + $Step * $i;
+                            $YPos = $AxisPos["T"];
+
+                            if ($Abscissa != null) {
+                                $Value = "";
+                                if (isset($Data["Series"][$Abscissa]["Data"][$i])) {
+                                    $Value = $this->scaleFormat(
+                                        $Data["Series"][$Abscissa]["Data"][$i],
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            } else {
+                                $Value = $i;
+                                if (isset($Parameters["ScaleMin"]) && isset($Parameters["RowHeight"])) {
+                                    $Value = $this->scaleFormat(
+                                        $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            }
+
+                            $ID++;
+                            $Skipped = true;
+                            if ($this->isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
+                                && !$RemoveXAxis
+                            ) {
+                                $Bounds = $this->drawText(
+                                    $XPos,
+                                    $YPos - $OuterTickWidth - $YLabelOffset,
+                                    $Value,
+                                    ["Angle" => $LabelRotation, "Align" => $LabelAlign]
+                                );
+                                $TxtBox = $YPos - $OuterTickWidth - 2 - ($Bounds[0]["Y"] - $Bounds[2]["Y"]);
+                                $MinTop = min($MinTop, $TxtBox);
+                                $LastValue = $Value;
+                                $Skipped = false;
+                            }
+
+                            if ($RemoveXAxis) {
+                                $Skipped = false;
+                            }
+
+                            if ($Skipped) {
+                                if ($DrawXLines && !$RemoveSkippedAxis) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $this->GraphAreaY1 + $FloatingOffset,
+                                        $XPos,
+                                        $this->GraphAreaY2 - $FloatingOffset,
+                                        $SkippedAxisColor
+                                    );
+                                }
+                                if (($SkippedInnerTickWidth != 0 || $SkippedOuterTickWidth != 0)
+                                    && !$RemoveXAxis
+                                    && !$RemoveSkippedAxis
+                                ) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $YPos + $SkippedInnerTickWidth,
+                                        $XPos,
+                                        $YPos - $SkippedOuterTickWidth,
+                                        $SkippedTickColor
+                                    );
+                                }
+                            } else {
+                                if ($DrawXLines) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $this->GraphAreaY1 + $FloatingOffset,
+                                        $XPos,
+                                        $this->GraphAreaY2 - $FloatingOffset,
+                                        [
+                                            "R" => $GridR,
+                                            "G" => $GridG,
+                                            "B" => $GridB,
+                                            "Alpha" => $GridAlpha,
+                                            "Ticks" => $GridTicks
+                                        ]
+                                    );
+                                }
+                                if (($InnerTickWidth != 0 || $OuterTickWidth != 0) && !$RemoveXAxis) {
+                                    $this->drawLine(
+                                        $XPos,
+                                        $YPos + $InnerTickWidth,
+                                        $XPos,
+                                        $YPos - $OuterTickWidth,
+                                        [
+                                            "R" => $TickR,
+                                            "G" => $TickG,
+                                            "B" => $TickB,
+                                            "Alpha" => $TickAlpha
+                                        ]
+                                    );
+                                }
+                            }
+                        }
+
+                        if (isset($Parameters["Name"]) && !$RemoveXAxis) {
+                            $YPos = $MinTop - 2;
+                            $XPos = $this->GraphAreaX1 + ($this->GraphAreaX2 - $this->GraphAreaX1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                            );
+                            $MinTop = $Bounds[2]["Y"];
+
+                            $this->DataSet->Data["GraphArea"]["Y1"] = $MinTop;
+                        }
+
+                        $AxisPos["T"] = $MinTop - $ScaleSpacing;
+                    }
+                } elseif ($Pos == SCALE_POS_TOPBOTTOM) {
+                    if ($Parameters["Position"] == AXIS_POSITION_LEFT) {
+                        if ($LabelRotation == 0) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $XLabelOffset = -2;
+                        }
+                        if ($LabelRotation > 0 && $LabelRotation < 190) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $XLabelOffset = -6;
+                        }
+                        if ($LabelRotation == 180) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $XLabelOffset = -2;
+                        }
+                        if ($LabelRotation > 180 && $LabelRotation < 360) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $XLabelOffset = -5;
+                        }
+
+                        if (!$RemoveXAxis) {
+                            if ($Floating) {
+                                $FloatingOffset = $YMargin;
+                                $this->drawLine(
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY1 + $Parameters["Margin"],
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY2 - $Parameters["Margin"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            } else {
+                                $FloatingOffset = 0;
+                                $this->drawLine(
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY1,
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY2,
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            }
+
+                            if ($DrawArrows) {
+                                $this->drawArrow(
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY2 - $Parameters["Margin"],
+                                    $AxisPos["L"],
+                                    $this->GraphAreaY2 + ($ArrowSize * 2),
+                                    [
+                                        "FillR" => $AxisR,
+                                        "FillG" => $AxisG,
+                                        "FillB" => $AxisB,
+                                        "Size" => $ArrowSize
+                                    ]
+                                );
+                            }
+                        }
+
+                        $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Parameters["Margin"] * 2;
+
+                        if ($Parameters["Rows"] == 0) {
+                            $Step = $Height;
+                        } else {
+                            $Step = $Height / $Parameters["Rows"];
+                        }
+
+                        $MinLeft = $AxisPos["L"];
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $YPos = $this->GraphAreaY1 + $Parameters["Margin"] + $Step * $i;
+                            $XPos = $AxisPos["L"];
+
+                            if ($Abscissa != null) {
+                                $Value = "";
+                                if (isset($Data["Series"][$Abscissa]["Data"][$i])) {
+                                    $Value = $this->scaleFormat(
+                                        $Data["Series"][$Abscissa]["Data"][$i],
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            } else {
+                                $Value = $i;
+                                if (isset($Parameters["ScaleMin"]) && isset($Parameters["RowHeight"])) {
+                                    $Value = $this->scaleFormat(
+                                        $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            }
+
+                            $ID++;
+                            $Skipped = true;
+                            if ($this->isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
+                                && !$RemoveXAxis
+                            ) {
+                                $Bounds = $this->drawText(
+                                    $XPos - $OuterTickWidth + $XLabelOffset,
+                                    $YPos,
+                                    $Value,
+                                    ["Angle" => $LabelRotation, "Align" => $LabelAlign]
+                                );
+                                $TxtBox = $XPos - $OuterTickWidth - 2 - ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                                $MinLeft = min($MinLeft, $TxtBox);
+                                $LastValue = $Value;
+                                $Skipped = false;
+                            }
+
+                            if ($RemoveXAxis) {
+                                $Skipped = false;
+                            }
+
+                            if ($Skipped) {
+                                if ($DrawXLines && !$RemoveSkippedAxis) {
+                                    $this->drawLine(
+                                        $this->GraphAreaX1 + $FloatingOffset,
+                                        $YPos,
+                                        $this->GraphAreaX2 - $FloatingOffset,
+                                        $YPos,
+                                        $SkippedAxisColor
+                                    );
+                                }
+                                if (($SkippedInnerTickWidth != 0 || $SkippedOuterTickWidth != 0)
+                                    && !$RemoveXAxis
+                                    && !$RemoveSkippedAxis
+                                ) {
+                                    $this->drawLine(
+                                        $XPos - $SkippedOuterTickWidth,
+                                        $YPos,
+                                        $XPos + $SkippedInnerTickWidth,
+                                        $YPos,
+                                        $SkippedTickColor
+                                    );
+                                }
+                            } else {
+                                if ($DrawXLines &&
+                                    ($YPos != $this->GraphAreaY1 && $YPos != $this->GraphAreaY2)
+                                ) {
+                                    $this->drawLine(
+                                        $this->GraphAreaX1 + $FloatingOffset,
+                                        $YPos,
+                                        $this->GraphAreaX2 - $FloatingOffset,
+                                        $YPos,
+                                        [
+                                            "R" => $GridR,
+                                            "G" => $GridG,
+                                            "B" => $GridB,
+                                            "Alpha" => $GridAlpha,
+                                            "Ticks" => $GridTicks
+                                        ]
+                                    );
+                                }
+                                if (($InnerTickWidth != 0 || $OuterTickWidth != 0) && !$RemoveXAxis) {
+                                    $this->drawLine(
+                                        $XPos - $OuterTickWidth,
+                                        $YPos,
+                                        $XPos + $InnerTickWidth,
+                                        $YPos,
+                                        ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                                    );
+                                }
+                            }
+                        }
+                        if (isset($Parameters["Name"]) && !$RemoveXAxis) {
+                            $XPos = $MinLeft - 2;
+                            $YPos = $this->GraphAreaY1 + ($this->GraphAreaY2 - $this->GraphAreaY1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 90]
+                            );
+                            $MinLeft = $Bounds[0]["X"];
+
+                            $this->DataSet->Data["GraphArea"]["X1"] = $MinLeft;
+                        }
+
+                        $AxisPos["L"] = $MinLeft - $ScaleSpacing;
+                    } elseif ($Parameters["Position"] == AXIS_POSITION_RIGHT) {
+                        if ($LabelRotation == 0) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $XLabelOffset = 2;
+                        }
+                        if ($LabelRotation > 0 && $LabelRotation < 190) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLELEFT;
+                            $XLabelOffset = 6;
+                        }
+                        if ($LabelRotation == 180) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $XLabelOffset = 5;
+                        }
+                        if ($LabelRotation > 180 && $LabelRotation < 360) {
+                            $LabelAlign = TEXT_ALIGN_MIDDLERIGHT;
+                            $XLabelOffset = 7;
+                        }
+
+                        if (!$RemoveXAxis) {
+                            if ($Floating) {
+                                $FloatingOffset = $YMargin;
+                                $this->drawLine(
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY1 + $Parameters["Margin"],
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY2 - $Parameters["Margin"],
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            } else {
+                                $FloatingOffset = 0;
+                                $this->drawLine(
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY1,
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY2,
+                                    ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                                );
+                            }
+
+                            if ($DrawArrows) {
+                                $this->drawArrow(
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY2 - $Parameters["Margin"],
+                                    $AxisPos["R"],
+                                    $this->GraphAreaY2 + ($ArrowSize * 2),
+                                    [
+                                        "FillR" => $AxisR,
+                                        "FillG" => $AxisG,
+                                        "FillB" => $AxisB,
+                                        "Size" => $ArrowSize
+                                    ]
+                                );
+                            }
+                        }
+
+                        $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Parameters["Margin"] * 2;
+
+                        if ($Parameters["Rows"] == 0) {
+                            $Step = $Height;
+                        } else {
+                            $Step = $Height / $Parameters["Rows"];
+                        }
+
+                        $MaxRight = $AxisPos["R"];
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $YPos = $this->GraphAreaY1 + $Parameters["Margin"] + $Step * $i;
+                            $XPos = $AxisPos["R"];
+
+                            if ($Abscissa != null) {
+                                $Value = "";
+                                if (isset($Data["Series"][$Abscissa]["Data"][$i])) {
+                                    $Value = $this->scaleFormat(
+                                        $Data["Series"][$Abscissa]["Data"][$i],
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            } else {
+                                $Value = $i;
+                                if (isset($Parameters["ScaleMin"]) && isset($Parameters["RowHeight"])) {
+                                    $Value = $this->scaleFormat(
+                                        $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                        $Data["XAxisDisplay"],
+                                        $Data["XAxisFormat"],
+                                        $Data["XAxisUnit"]
+                                    );
+                                }
+                            }
+
+                            $ID++;
+                            $Skipped = true;
+                            if ($this->isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
+                                && !$RemoveXAxis
+                            ) {
+                                $Bounds = $this->drawText(
+                                    $XPos + $OuterTickWidth + $XLabelOffset,
+                                    $YPos,
+                                    $Value,
+                                    ["Angle" => $LabelRotation, "Align" => $LabelAlign]
+                                );
+                                $TxtBox = $XPos + $OuterTickWidth + 2 + ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                                $MaxRight = max($MaxRight, $TxtBox);
+                                $LastValue = $Value;
+                                $Skipped = false;
+                            }
+
+                            if ($RemoveXAxis) {
+                                $Skipped = false;
+                            }
+
+                            if ($Skipped) {
+                                if ($DrawXLines && !$RemoveSkippedAxis) {
+                                    $this->drawLine(
+                                        $this->GraphAreaX1 + $FloatingOffset,
+                                        $YPos,
+                                        $this->GraphAreaX2 - $FloatingOffset,
+                                        $YPos,
+                                        $SkippedAxisColor
+                                    );
+                                }
+                                if (($SkippedInnerTickWidth != 0 || $SkippedOuterTickWidth != 0)
+                                    && !$RemoveXAxis
+                                    && !$RemoveSkippedAxis
+                                ) {
+                                    $this->drawLine(
+                                        $XPos + $SkippedOuterTickWidth,
+                                        $YPos,
+                                        $XPos - $SkippedInnerTickWidth,
+                                        $YPos,
+                                        $SkippedTickColor
+                                    );
+                                }
+                            } else {
+                                if ($DrawXLines) {
+                                    $this->drawLine(
+                                        $this->GraphAreaX1 + $FloatingOffset,
+                                        $YPos,
+                                        $this->GraphAreaX2 - $FloatingOffset,
+                                        $YPos,
+                                        [
+                                            "R" => $GridR,
+                                            "G" => $GridG,
+                                            "B" => $GridB,
+                                            "Alpha" => $GridAlpha,
+                                            "Ticks" => $GridTicks
+                                        ]
+                                    );
+                                }
+                                if (($InnerTickWidth != 0 || $OuterTickWidth != 0) && !$RemoveXAxis) {
+                                    $this->drawLine(
+                                        $XPos + $OuterTickWidth,
+                                        $YPos,
+                                        $XPos - $InnerTickWidth,
+                                        $YPos,
+                                        [
+                                            "R" => $TickR,
+                                            "G" => $TickG,
+                                            "B" => $TickB,
+                                            "Alpha" => $TickAlpha
+                                        ]
+                                    );
+                                }
+                            }
+                        }
+
+                        if (isset($Parameters["Name"]) && !$RemoveXAxis) {
+                            $XPos = $MaxRight + 4;
+                            $YPos = $this->GraphAreaY1 + ($this->GraphAreaY2 - $this->GraphAreaY1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 270]
+                            );
+                            $MaxRight = $Bounds[1]["X"];
+
+                            $this->DataSet->Data["GraphArea"]["X2"] = $MaxRight + $this->FontSize;
+                        }
+
+                        $AxisPos["R"] = $MaxRight + $ScaleSpacing;
+                    }
+                }
+            }
+
+            if ($Parameters["Identity"] == AXIS_Y && !$RemoveYAxis) {
+                if ($Pos == SCALE_POS_LEFTRIGHT) {
+                    if ($Parameters["Position"] == AXIS_POSITION_LEFT) {
+                        if ($Floating) {
+                            $FloatingOffset = $XMargin;
+                            $this->drawLine(
+                                $AxisPos["L"],
+                                $this->GraphAreaY1 + $Parameters["Margin"],
+                                $AxisPos["L"],
+                                $this->GraphAreaY2 - $Parameters["Margin"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        } else {
+                            $FloatingOffset = 0;
+                            $this->drawLine(
+                                $AxisPos["L"],
+                                $this->GraphAreaY1,
+                                $AxisPos["L"],
+                                $this->GraphAreaY2,
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        }
+
+                        if ($DrawArrows) {
+                            $this->drawArrow(
+                                $AxisPos["L"],
+                                $this->GraphAreaY1 + $Parameters["Margin"],
+                                $AxisPos["L"],
+                                $this->GraphAreaY1 - ($ArrowSize * 2),
+                                [
+                                    "FillR" => $AxisR,
+                                    "FillG" => $AxisG,
+                                    "FillB" => $AxisB,
+                                    "Size" => $ArrowSize
+                                ]
+                            );
+                        }
+
+                        $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Parameters["Margin"] * 2;
+                        $Step = $Height / $Parameters["Rows"];
+                        $SubTicksSize = $Step / 2;
+                        $MinLeft = $AxisPos["L"];
+                        $LastY = null;
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $YPos = $this->GraphAreaY2 - $Parameters["Margin"] - $Step * $i;
+                            $XPos = $AxisPos["L"];
+                            $Value = $this->scaleFormat(
+                                $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                $Parameters["Display"],
+                                $Parameters["Format"],
+                                $Parameters["Unit"]
+                            );
+
+                            if ($i % 2 == 1) {
+                                $BGColor = [
+                                    "R" => $BackgroundR1,
+                                    "G" => $BackgroundG1,
+                                    "B" => $BackgroundB1,
+                                    "Alpha" => $BackgroundAlpha1
+                                ];
+                            } else {
+                                $BGColor = [
+                                    "R" => $BackgroundR2,
+                                    "G" => $BackgroundG2,
+                                    "B" => $BackgroundB2,
+                                    "Alpha" => $BackgroundAlpha2
+                                ];
+                            }
+                            if ($LastY != null
+                                && $CycleBackground
+                                && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                            ) {
+                                $this->drawFilledRectangle(
+                                    $this->GraphAreaX1 + $FloatingOffset,
+                                    $LastY,
+                                    $this->GraphAreaX2 - $FloatingOffset,
+                                    $YPos,
+                                    $BGColor
+                                );
+                            }
+
+                            if ($DrawYLines == ALL || in_array($AxisID, $DrawYLines)) {
+                                $this->drawLine(
+                                    $this->GraphAreaX1 + $FloatingOffset,
+                                    $YPos,
+                                    $this->GraphAreaX2 - $FloatingOffset,
+                                    $YPos,
+                                    [
+                                        "R" => $GridR,
+                                        "G" => $GridG,
+                                        "B" => $GridB,
+                                        "Alpha" => $GridAlpha,
+                                        "Ticks" => $GridTicks
+                                    ]
+                                );
+                            }
+
+                            if ($DrawSubTicks && $i != $Parameters["Rows"]) {
+                                $this->drawLine(
+                                    $XPos - $OuterSubTickWidth,
+                                    $YPos - $SubTicksSize,
+                                    $XPos + $InnerSubTickWidth,
+                                    $YPos - $SubTicksSize,
+                                    [
+                                        "R" => $SubTickR,
+                                        "G" => $SubTickG,
+                                        "B" => $SubTickB,
+                                        "Alpha" => $SubTickAlpha
+                                    ]
+                                );
+                            }
+                            if (!$RemoveYAxiValues) {
+                                $this->drawLine(
+                                    $XPos - $OuterTickWidth,
+                                    $YPos,
+                                    $XPos + $InnerTickWidth,
+                                    $YPos,
+                                    ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                                );
+                                $Bounds = $this->drawText(
+                                    $XPos - $OuterTickWidth - 2,
+                                    $YPos,
+                                    $Value,
+                                    ["Align" => TEXT_ALIGN_MIDDLERIGHT]
+                                );
+                                $TxtLeft = $XPos - $OuterTickWidth - 2 - ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                                $MinLeft = min($MinLeft, $TxtLeft);
+                            }
+
+                            $LastY = $YPos;
+                        }
+
+                        if (isset($Parameters["Name"])) {
+                            $XPos = $MinLeft - 2;
+                            $YPos = $this->GraphAreaY1 + ($this->GraphAreaY2 - $this->GraphAreaY1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 90]
+                            );
+                            $MinLeft = $Bounds[2]["X"];
+
+                            $this->DataSet->Data["GraphArea"]["X1"] = $MinLeft;
+                        }
+
+                        $AxisPos["L"] = $MinLeft - $ScaleSpacing;
+                    } elseif ($Parameters["Position"] == AXIS_POSITION_RIGHT) {
+                        if ($Floating) {
+                            $FloatingOffset = $XMargin;
+                            $this->drawLine(
+                                $AxisPos["R"],
+                                $this->GraphAreaY1 + $Parameters["Margin"],
+                                $AxisPos["R"],
+                                $this->GraphAreaY2 - $Parameters["Margin"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        } else {
+                            $FloatingOffset = 0;
+                            $this->drawLine(
+                                $AxisPos["R"],
+                                $this->GraphAreaY1,
+                                $AxisPos["R"],
+                                $this->GraphAreaY2,
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        }
+
+                        if ($DrawArrows) {
+                            $this->drawArrow(
+                                $AxisPos["R"],
+                                $this->GraphAreaY1 + $Parameters["Margin"],
+                                $AxisPos["R"],
+                                $this->GraphAreaY1 - ($ArrowSize * 2),
+                                [
+                                    "FillR" => $AxisR,
+                                    "FillG" => $AxisG,
+                                    "FillB" => $AxisB,
+                                    "Size" => $ArrowSize
+                                ]
+                            );
+                        }
+
+                        $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Parameters["Margin"] * 2;
+                        $Step = $Height / $Parameters["Rows"];
+                        $SubTicksSize = $Step / 2;
+                        $MaxLeft = $AxisPos["R"];
+                        $LastY = null;
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $YPos = $this->GraphAreaY2 - $Parameters["Margin"] - $Step * $i;
+                            $XPos = $AxisPos["R"];
+                            $Value = $this->scaleFormat(
+                                $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                $Parameters["Display"],
+                                $Parameters["Format"],
+                                $Parameters["Unit"]
+                            );
+
+                            if ($i % 2 == 1) {
+                                $BGColor = [
+                                    "R" => $BackgroundR1,
+                                    "G" => $BackgroundG1,
+                                    "B" => $BackgroundB1,
+                                    "Alpha" => $BackgroundAlpha1
+                                ];
+                            } else {
+                                $BGColor = [
+                                    "R" => $BackgroundR2,
+                                    "G" => $BackgroundG2,
+                                    "B" => $BackgroundB2,
+                                    "Alpha" => $BackgroundAlpha2
+                                ];
+                            }
+                            if ($LastY != null
+                                && $CycleBackground
+                                && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                            ) {
+                                $this->drawFilledRectangle(
+                                    $this->GraphAreaX1 + $FloatingOffset,
+                                    $LastY,
+                                    $this->GraphAreaX2 - $FloatingOffset,
+                                    $YPos,
+                                    $BGColor
+                                );
+                            }
+
+                            if ($DrawYLines == ALL || in_array($AxisID, $DrawYLines)) {
+                                $this->drawLine(
+                                    $this->GraphAreaX1 + $FloatingOffset,
+                                    $YPos,
+                                    $this->GraphAreaX2 - $FloatingOffset,
+                                    $YPos,
+                                    [
+                                        "R" => $GridR,
+                                        "G" => $GridG,
+                                        "B" => $GridB,
+                                        "Alpha" => $GridAlpha,
+                                        "Ticks" => $GridTicks
+                                    ]
+                                );
+                            }
+
+                            if ($DrawSubTicks && $i != $Parameters["Rows"]) {
+                                $this->drawLine(
+                                    $XPos - $OuterSubTickWidth,
+                                    $YPos - $SubTicksSize,
+                                    $XPos + $InnerSubTickWidth,
+                                    $YPos - $SubTicksSize,
+                                    [
+                                        "R" => $SubTickR,
+                                        "G" => $SubTickG,
+                                        "B" => $SubTickB,
+                                        "Alpha" => $SubTickAlpha
+                                    ]
+                                );
+                            }
+                            $this->drawLine(
+                                $XPos - $InnerTickWidth,
+                                $YPos,
+                                $XPos + $OuterTickWidth,
+                                $YPos,
+                                ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                            );
+                            $Bounds = $this->drawText(
+                                $XPos + $OuterTickWidth + 2,
+                                $YPos,
+                                $Value,
+                                ["Align" => TEXT_ALIGN_MIDDLELEFT]
+                            );
+                            $TxtLeft = $XPos + $OuterTickWidth + 2 + ($Bounds[1]["X"] - $Bounds[0]["X"]);
+                            $MaxLeft = max($MaxLeft, $TxtLeft);
+
+                            $LastY = $YPos;
+                        }
+
+                        if (isset($Parameters["Name"])) {
+                            $XPos = $MaxLeft + 6;
+                            $YPos = $this->GraphAreaY1 + ($this->GraphAreaY2 - $this->GraphAreaY1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE, "Angle" => 270]
+                            );
+                            $MaxLeft = $Bounds[2]["X"];
+
+                            $this->DataSet->Data["GraphArea"]["X2"] = $MaxLeft + $this->FontSize;
+                        }
+                        $AxisPos["R"] = $MaxLeft + $ScaleSpacing;
+                    }
+                } elseif ($Pos == SCALE_POS_TOPBOTTOM) {
+                    if ($Parameters["Position"] == AXIS_POSITION_TOP) {
+                        if ($Floating) {
+                            $FloatingOffset = $XMargin;
+                            $this->drawLine(
+                                $this->GraphAreaX1 + $Parameters["Margin"],
+                                $AxisPos["T"],
+                                $this->GraphAreaX2 - $Parameters["Margin"],
+                                $AxisPos["T"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        } else {
+                            $FloatingOffset = 0;
+                            $this->drawLine(
+                                $this->GraphAreaX1,
+                                $AxisPos["T"],
+                                $this->GraphAreaX2,
+                                $AxisPos["T"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        }
+
+                        if ($DrawArrows) {
+                            $this->drawArrow(
+                                $this->GraphAreaX2 - $Parameters["Margin"],
+                                $AxisPos["T"],
+                                $this->GraphAreaX2 + ($ArrowSize * 2),
+                                $AxisPos["T"],
+                                [
+                                    "FillR" => $AxisR,
+                                    "FillG" => $AxisG,
+                                    "FillB" => $AxisB,
+                                    "Size" => $ArrowSize
+                                ]
+                            );
+                        }
+
+                        $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Parameters["Margin"] * 2;
+                        $Step = $Width / $Parameters["Rows"];
+                        $SubTicksSize = $Step / 2;
+                        $MinTop = $AxisPos["T"];
+                        $LastX = null;
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $XPos = $this->GraphAreaX1 + $Parameters["Margin"] + $Step * $i;
+                            $YPos = $AxisPos["T"];
+                            $Value = $this->scaleFormat(
+                                $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                $Parameters["Display"],
+                                $Parameters["Format"],
+                                $Parameters["Unit"]
+                            );
+
+                            if ($i % 2 == 1) {
+                                $BGColor = [
+                                    "R" => $BackgroundR1,
+                                    "G" => $BackgroundG1,
+                                    "B" => $BackgroundB1,
+                                    "Alpha" => $BackgroundAlpha1
+                                ];
+                            } else {
+                                $BGColor = [
+                                    "R" => $BackgroundR2,
+                                    "G" => $BackgroundG2,
+                                    "B" => $BackgroundB2,
+                                    "Alpha" => $BackgroundAlpha2
+                                ];
+                            }
+                            if ($LastX != null
+                                && $CycleBackground
+                                && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                            ) {
+                                $this->drawFilledRectangle(
+                                    $LastX,
+                                    $this->GraphAreaY1 + $FloatingOffset,
+                                    $XPos,
+                                    $this->GraphAreaY2 - $FloatingOffset,
+                                    $BGColor
+                                );
+                            }
+
+                            if ($DrawYLines == ALL || in_array($AxisID, $DrawYLines)) {
+                                $this->drawLine(
+                                    $XPos,
+                                    $this->GraphAreaY1 + $FloatingOffset,
+                                    $XPos,
+                                    $this->GraphAreaY2 - $FloatingOffset,
+                                    [
+                                        "R" => $GridR,
+                                        "G" => $GridG,
+                                        "B" => $GridB,
+                                        "Alpha" => $GridAlpha,
+                                        "Ticks" => $GridTicks
+                                    ]
+                                );
+                            }
+
+                            if ($DrawSubTicks && $i != $Parameters["Rows"]) {
+                                $this->drawLine(
+                                    $XPos + $SubTicksSize,
+                                    $YPos - $OuterSubTickWidth,
+                                    $XPos + $SubTicksSize,
+                                    $YPos + $InnerSubTickWidth,
+                                    [
+                                        "R" => $SubTickR,
+                                        "G" => $SubTickG,
+                                        "B" => $SubTickB,
+                                        "Alpha" => $SubTickAlpha
+                                    ]
+                                );
+                            }
+                            $this->drawLine(
+                                $XPos,
+                                $YPos - $OuterTickWidth,
+                                $XPos,
+                                $YPos + $InnerTickWidth,
+                                ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                            );
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos - $OuterTickWidth - 2,
+                                $Value,
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                            );
+                            $TxtHeight = $YPos - $OuterTickWidth - 2 - ($Bounds[1]["Y"] - $Bounds[2]["Y"]);
+                            $MinTop = min($MinTop, $TxtHeight);
+
+                            $LastX = $XPos;
+                        }
+
+                        if (isset($Parameters["Name"])) {
+                            $YPos = $MinTop - 2;
+                            $XPos = $this->GraphAreaX1 + ($this->GraphAreaX2 - $this->GraphAreaX1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_BOTTOMMIDDLE]
+                            );
+                            $MinTop = $Bounds[2]["Y"];
+
+                            $this->DataSet->Data["GraphArea"]["Y1"] = $MinTop;
+                        }
+
+                        $AxisPos["T"] = $MinTop - $ScaleSpacing;
+                    } elseif ($Parameters["Position"] == AXIS_POSITION_BOTTOM) {
+                        if ($Floating) {
+                            $FloatingOffset = $XMargin;
+                            $this->drawLine(
+                                $this->GraphAreaX1 + $Parameters["Margin"],
+                                $AxisPos["B"],
+                                $this->GraphAreaX2 - $Parameters["Margin"],
+                                $AxisPos["B"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        } else {
+                            $FloatingOffset = 0;
+                            $this->drawLine(
+                                $this->GraphAreaX1,
+                                $AxisPos["B"],
+                                $this->GraphAreaX2,
+                                $AxisPos["B"],
+                                ["R" => $AxisR, "G" => $AxisG, "B" => $AxisB, "Alpha" => $AxisAlpha]
+                            );
+                        }
+
+                        if ($DrawArrows) {
+                            $this->drawArrow(
+                                $this->GraphAreaX2 - $Parameters["Margin"],
+                                $AxisPos["B"],
+                                $this->GraphAreaX2 + ($ArrowSize * 2),
+                                $AxisPos["B"],
+                                [
+                                    "FillR" => $AxisR,
+                                    "FillG" => $AxisG,
+                                    "FillB" => $AxisB,
+                                    "Size" => $ArrowSize
+                                ]
+                            );
+                        }
+
+                        $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Parameters["Margin"] * 2;
+                        $Step = $Width / $Parameters["Rows"];
+                        $SubTicksSize = $Step / 2;
+                        $MaxBottom = $AxisPos["B"];
+                        $LastX = null;
+                        for ($i = 0; $i <= $Parameters["Rows"]; $i++) {
+                            $XPos = $this->GraphAreaX1 + $Parameters["Margin"] + $Step * $i;
+                            $YPos = $AxisPos["B"];
+                            $Value = $this->scaleFormat(
+                                $Parameters["ScaleMin"] + $Parameters["RowHeight"] * $i,
+                                $Parameters["Display"],
+                                $Parameters["Format"],
+                                $Parameters["Unit"]
+                            );
+
+                            if ($i % 2 == 1) {
+                                $BGColor = [
+                                    "R" => $BackgroundR1,
+                                    "G" => $BackgroundG1,
+                                    "B" => $BackgroundB1,
+                                    "Alpha" => $BackgroundAlpha1
+                                ];
+                            } else {
+                                $BGColor = [
+                                    "R" => $BackgroundR2,
+                                    "G" => $BackgroundG2,
+                                    "B" => $BackgroundB2,
+                                    "Alpha" => $BackgroundAlpha2
+                                ];
+                            }
+                            if ($LastX != null
+                                && $CycleBackground
+                                && ($DrawYLines == ALL || in_array($AxisID, $DrawYLines))
+                            ) {
+                                $this->drawFilledRectangle(
+                                    $LastX,
+                                    $this->GraphAreaY1 + $FloatingOffset,
+                                    $XPos,
+                                    $this->GraphAreaY2 - $FloatingOffset,
+                                    $BGColor
+                                );
+                            }
+
+                            if ($DrawYLines == ALL || in_array($AxisID, $DrawYLines)) {
+                                $this->drawLine(
+                                    $XPos,
+                                    $this->GraphAreaY1 + $FloatingOffset,
+                                    $XPos,
+                                    $this->GraphAreaY2 - $FloatingOffset,
+                                    [
+                                        "R" => $GridR,
+                                        "G" => $GridG,
+                                        "B" => $GridB,
+                                        "Alpha" => $GridAlpha,
+                                        "Ticks" => $GridTicks
+                                    ]
+                                );
+                            }
+
+                            if ($DrawSubTicks && $i != $Parameters["Rows"]) {
+                                $this->drawLine(
+                                    $XPos + $SubTicksSize,
+                                    $YPos - $OuterSubTickWidth,
+                                    $XPos + $SubTicksSize,
+                                    $YPos + $InnerSubTickWidth,
+                                    [
+                                        "R" => $SubTickR,
+                                        "G" => $SubTickG,
+                                        "B" => $SubTickB,
+                                        "Alpha" => $SubTickAlpha
+                                    ]
+                                );
+                            }
+                            $this->drawLine(
+                                $XPos,
+                                $YPos - $OuterTickWidth,
+                                $XPos,
+                                $YPos + $InnerTickWidth,
+                                ["R" => $TickR, "G" => $TickG, "B" => $TickB, "Alpha" => $TickAlpha]
+                            );
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos + $OuterTickWidth + 2,
+                                $Value,
+                                ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                            );
+                            $TxtHeight = $YPos + $OuterTickWidth + 2 + ($Bounds[1]["Y"] - $Bounds[2]["Y"]);
+                            $MaxBottom = max($MaxBottom, $TxtHeight);
+
+                            $LastX = $XPos;
+                        }
+
+                        if (isset($Parameters["Name"])) {
+                            $YPos = $MaxBottom + 2;
+                            $XPos = $this->GraphAreaX1 + ($this->GraphAreaX2 - $this->GraphAreaX1) / 2;
+                            $Bounds = $this->drawText(
+                                $XPos,
+                                $YPos,
+                                $Parameters["Name"],
+                                ["Align" => TEXT_ALIGN_TOPMIDDLE]
+                            );
+                            $MaxBottom = $Bounds[0]["Y"];
+
+                            $this->DataSet->Data["GraphArea"]["Y2"] = $MaxBottom + $this->FontSize;
+                        }
+
+                        $AxisPos["B"] = $MaxBottom + $ScaleSpacing;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw an X threshold
+     * @param mixed $Value
+     * @param boolean $Format
+     * @return array|null|integer
+     */
+    public function drawXThreshold($Value, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 50;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 6;
+        $Wide = isset($Format["Wide"]) ? $Format["Wide"] : false;
+        $WideFactor = isset($Format["WideFactor"]) ? $Format["WideFactor"] : 5;
+        $WriteCaption = isset($Format["WriteCaption"]) ? $Format["WriteCaption"] : false;
+        $Caption = isset($Format["Caption"]) ? $Format["Caption"] : null;
+        $CaptionAlign = isset($Format["CaptionAlign"]) ? $Format["CaptionAlign"] : CAPTION_LEFT_TOP;
+        $CaptionOffset = isset($Format["CaptionOffset"]) ? $Format["CaptionOffset"] : 5;
+        $CaptionR = isset($Format["CaptionR"]) ? $Format["CaptionR"] : 255;
+        $CaptionG = isset($Format["CaptionG"]) ? $Format["CaptionG"] : 255;
+        $CaptionB = isset($Format["CaptionB"]) ? $Format["CaptionB"] : 255;
+        $CaptionAlpha = isset($Format["CaptionAlpha"]) ? $Format["CaptionAlpha"] : 100;
+        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : true;
+        $DrawBoxBorder = isset($Format["DrawBoxBorder"]) ? $Format["DrawBoxBorder"] : false;
+        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 3;
+        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : true;
+        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 3;
+        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
+        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
+        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 30;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
+        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 255;
+        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 255;
+        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 255;
+        $BoxBorderAlpha = isset($Format["BoxBorderAlpha"]) ? $Format["BoxBorderAlpha"] : 100;
+        $ValueIsLabel = isset($Format["ValueIsLabel"]) ? $Format["ValueIsLabel"] : false;
+
+        $Data = $this->DataSet->getData();
+        $AbscissaMargin = $this->getAbscissaMargin($Data);
+        $XScale = $this->scaleGetXSettings();
+
+        if (is_array($Value)) {
+            foreach ($Value as $Key => $ID) {
+                $this->drawXThreshold($ID, $Format);
+            }
+            return 0;
+        }
+
+        if ($ValueIsLabel) {
+            $Format["ValueIsLabel"] = false;
+            foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $SerieValue) {
+                if ($SerieValue == $Value) {
+                    $this->drawXThreshold($Key, $Format);
+                }
+            }
+            return 0;
+        }
+
+        $CaptionSettings = [
+            "DrawBox" => $DrawBox,
+            "DrawBoxBorder" => $DrawBoxBorder,
+            "BorderOffset" => $BorderOffset,
+            "BoxRounded" => $BoxRounded,
+            "RoundedRadius" => $RoundedRadius,
+            "BoxR" => $BoxR,
+            "BoxG" => $BoxG,
+            "BoxB" => $BoxB,
+            "BoxAlpha" => $BoxAlpha,
+            "BoxSurrounding" => $BoxSurrounding,
+            "BoxBorderR" => $BoxBorderR,
+            "BoxBorderG" => $BoxBorderG,
+            "BoxBorderB" => $BoxBorderB,
+            "BoxBorderAlpha" => $BoxBorderAlpha,
+            "R" => $CaptionR,
+            "G" => $CaptionG,
+            "B" => $CaptionB,
+            "Alpha" => $CaptionAlpha
+        ];
+
+        if ($Caption == null) {
+            $Caption = $Value;
+            if (isset($Data["Abscissa"])
+                && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Value])
+            ) {
+                $Caption = $Data["Series"][$Data["Abscissa"]]["Data"][$Value];
+            }
+        }
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $XStep = (($this->GraphAreaX2 - $this->GraphAreaX1) - $XScale[0] * 2) / $XScale[1];
+            $XPos = $this->GraphAreaX1 + $XScale[0] + $XStep * $Value;
+            $YPos1 = $this->GraphAreaY1 + $Data["YMargin"];
+            $YPos2 = $this->GraphAreaY2 - $Data["YMargin"];
+
+            if ($XPos >= $this->GraphAreaX1 + $AbscissaMargin
+                && $XPos <= $this->GraphAreaX2 - $AbscissaMargin
+            ) {
+                $this->drawLine(
+                    $XPos,
+                    $YPos1,
+                    $XPos,
+                    $YPos2,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $Ticks,
+                        "Weight" => $Weight
+                    ]
+                );
+
+                if ($Wide) {
+                    $this->drawLine(
+                        $XPos - 1,
+                        $YPos1,
+                        $XPos - 1,
+                        $YPos2,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                    $this->drawLine(
+                        $XPos + 1,
+                        $YPos1,
+                        $XPos + 1,
+                        $YPos2,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                }
+
+                if ($WriteCaption) {
+                    if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                        $Y = $YPos1 + $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                    } else {
+                        $Y = $YPos2 - $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
+                    }
+                    $this->drawText($XPos, $Y, $Caption, $CaptionSettings);
+                }
+
+                return ["X" => $XPos];
+            }
+        } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+            $XStep = (($this->GraphAreaY2 - $this->GraphAreaY1) - $XScale[0] * 2) / $XScale[1];
+            $XPos = $this->GraphAreaY1 + $XScale[0] + $XStep * $Value;
+            $YPos1 = $this->GraphAreaX1 + $Data["YMargin"];
+            $YPos2 = $this->GraphAreaX2 - $Data["YMargin"];
+
+            if ($XPos >= $this->GraphAreaY1 + $AbscissaMargin
+                && $XPos <= $this->GraphAreaY2 - $AbscissaMargin
+            ) {
+                $this->drawLine(
+                    $YPos1,
+                    $XPos,
+                    $YPos2,
+                    $XPos,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $Ticks,
+                        "Weight" => $Weight
+                    ]
+                );
+
+                if ($Wide) {
+                    $this->drawLine(
+                        $YPos1,
+                        $XPos - 1,
+                        $YPos2,
+                        $XPos - 1,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                    $this->drawLine(
+                        $YPos1,
+                        $XPos + 1,
+                        $YPos2,
+                        $XPos + 1,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                }
+
+                if ($WriteCaption) {
+                    if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                        $Y = $YPos1 + $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLELEFT;
+                    } else {
+                        $Y = $YPos2 - $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
+                    }
+
+                    $this->drawText($Y, $XPos, $Caption, $CaptionSettings);
+                }
+
+                return ["X" => $XPos];
+            }
+        }
+    }
+
+    /**
+     * Draw an X threshold area
+     * @param mixed $Value1
+     * @param mixed $Value2
+     * @param array $Format
+     * @return array|null
+     */
+    public function drawXThresholdArea($Value1, $Value2, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 20;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : true;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha + 20;
+        $BorderTicks = isset($Format["BorderTicks"]) ? $Format["BorderTicks"] : 2;
+        $AreaName = isset($Format["AreaName"]) ? $Format["AreaName"] : null;
+        $NameAngle = isset($Format["NameAngle"]) ? $Format["NameAngle"] : ZONE_NAME_ANGLE_AUTO;
+        $NameR = isset($Format["NameR"]) ? $Format["NameR"] : 255;
+        $NameG = isset($Format["NameG"]) ? $Format["NameG"] : 255;
+        $NameB = isset($Format["NameB"]) ? $Format["NameB"] : 255;
+        $NameAlpha = isset($Format["NameAlpha"]) ? $Format["NameAlpha"] : 100;
+        $DisableShadowOnArea = isset($Format["DisableShadowOnArea"]) ? $Format["DisableShadowOnArea"] : true;
+
+        $RestoreShadow = $this->Shadow;
+        if ($DisableShadowOnArea && $this->Shadow) {
+            $this->Shadow = false;
+        }
+
+        if ($BorderAlpha > 100) {
+            $BorderAlpha = 100;
+        }
+
+        $Data = $this->DataSet->getData();
+        $XScale = $this->scaleGetXSettings();
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $XStep = (($this->GraphAreaX2 - $this->GraphAreaX1) - $XScale[0] * 2) / $XScale[1];
+            $XPos1 = $this->GraphAreaX1 + $XScale[0] + $XStep * $Value1;
+            $XPos2 = $this->GraphAreaX1 + $XScale[0] + $XStep * $Value2;
+            $YPos1 = $this->GraphAreaY1 + $Data["YMargin"];
+            $YPos2 = $this->GraphAreaY2 - $Data["YMargin"];
+
+            if ($XPos1 < $this->GraphAreaX1 + $XScale[0]) {
+                $XPos1 = $this->GraphAreaX1 + $XScale[0];
+            }
+            if ($XPos1 > $this->GraphAreaX2 - $XScale[0]) {
+                $XPos1 = $this->GraphAreaX2 - $XScale[0];
+            }
+            if ($XPos2 < $this->GraphAreaX1 + $XScale[0]) {
+                $XPos2 = $this->GraphAreaX1 + $XScale[0];
+            }
+            if ($XPos2 > $this->GraphAreaX2 - $XScale[0]) {
+                $XPos2 = $this->GraphAreaX2 - $XScale[0];
+            }
+
+            $this->drawFilledRectangle(
+                $XPos1,
+                $YPos1,
+                $XPos2,
+                $YPos2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+
+            if ($Border) {
+                $this->drawLine(
+                    $XPos1,
+                    $YPos1,
+                    $XPos1,
+                    $YPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->drawLine(
+                    $XPos2,
+                    $YPos1,
+                    $XPos2,
+                    $YPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($XPos2 - $XPos1) / 2 + $XPos1;
+                $YPos = ($YPos2 - $YPos1) / 2 + $YPos1;
+
+                if ($NameAngle == ZONE_NAME_ANGLE_AUTO) {
+                    $TxtPos = $this->getTextBox(
+                        $XPos,
+                        $YPos,
+                        $this->FontName,
+                        $this->FontSize,
+                        0,
+                        $AreaName
+                    );
+                    $TxtWidth = $TxtPos[1]["X"] - $TxtPos[0]["X"];
+                    $NameAngle = 90;
+                    if (abs($XPos2 - $XPos1) > $TxtWidth) {
+                        $NameAngle = 0;
+                    }
+                }
+                $this->Shadow = $RestoreShadow;
+                $this->drawText(
+                    $XPos,
+                    $YPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => $NameAngle,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $this->Shadow = false;
+                }
+            }
+
+            $this->Shadow = $RestoreShadow;
+            return ["X1" => $XPos1, "X2" => $XPos2];
+        } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+            $XStep = (($this->GraphAreaY2 - $this->GraphAreaY1) - $XScale[0] * 2) / $XScale[1];
+            $XPos1 = $this->GraphAreaY1 + $XScale[0] + $XStep * $Value1;
+            $XPos2 = $this->GraphAreaY1 + $XScale[0] + $XStep * $Value2;
+            $YPos1 = $this->GraphAreaX1 + $Data["YMargin"];
+            $YPos2 = $this->GraphAreaX2 - $Data["YMargin"];
+
+            if ($XPos1 < $this->GraphAreaY1 + $XScale[0]) {
+                $XPos1 = $this->GraphAreaY1 + $XScale[0];
+            }
+            if ($XPos1 > $this->GraphAreaY2 - $XScale[0]) {
+                $XPos1 = $this->GraphAreaY2 - $XScale[0];
+            }
+            if ($XPos2 < $this->GraphAreaY1 + $XScale[0]) {
+                $XPos2 = $this->GraphAreaY1 + $XScale[0];
+            }
+            if ($XPos2 > $this->GraphAreaY2 - $XScale[0]) {
+                $XPos2 = $this->GraphAreaY2 - $XScale[0];
+            }
+
+            $this->drawFilledRectangle(
+                $YPos1,
+                $XPos1,
+                $YPos2,
+                $XPos2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+
+            if ($Border) {
+                $this->drawLine(
+                    $YPos1,
+                    $XPos1,
+                    $YPos2,
+                    $XPos1,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->drawLine(
+                    $YPos1,
+                    $XPos2,
+                    $YPos2,
+                    $XPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($XPos2 - $XPos1) / 2 + $XPos1;
+                $YPos = ($YPos2 - $YPos1) / 2 + $YPos1;
+
+                $this->Shadow = $RestoreShadow;
+                $this->drawText(
+                    $YPos,
+                    $XPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => 0,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $this->Shadow = false;
+                }
+            }
+
+            $this->Shadow = $RestoreShadow;
+            return ["X1" => $XPos1, "X2" => $XPos2];
+        }
+    }
+
+    /**
+     * Draw an Y threshold with the computed scale
+     * @param mixed $Value
+     * @param array $Format
+     * @return array|int
+     */
+    public function drawThreshold($Value, array $Format = [])
+    {
+        $AxisID = isset($Format["AxisID"]) ? $Format["AxisID"] : 0;
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 50;
+        $Weight = isset($Format["Weight"]) ? $Format["Weight"] : null;
+        $Ticks = isset($Format["Ticks"]) ? $Format["Ticks"] : 6;
+        $Wide = isset($Format["Wide"]) ? $Format["Wide"] : false;
+        $WideFactor = isset($Format["WideFactor"]) ? $Format["WideFactor"] : 5;
+        $WriteCaption = isset($Format["WriteCaption"]) ? $Format["WriteCaption"] : false;
+        $Caption = isset($Format["Caption"]) ? $Format["Caption"] : null;
+        $CaptionAlign = isset($Format["CaptionAlign"]) ? $Format["CaptionAlign"] : CAPTION_LEFT_TOP;
+        $CaptionOffset = isset($Format["CaptionOffset"]) ? $Format["CaptionOffset"] : 10;
+        $CaptionR = isset($Format["CaptionR"]) ? $Format["CaptionR"] : 255;
+        $CaptionG = isset($Format["CaptionG"]) ? $Format["CaptionG"] : 255;
+        $CaptionB = isset($Format["CaptionB"]) ? $Format["CaptionB"] : 255;
+        $CaptionAlpha = isset($Format["CaptionAlpha"]) ? $Format["CaptionAlpha"] : 100;
+        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : true;
+        $DrawBoxBorder = isset($Format["DrawBoxBorder"]) ? $Format["DrawBoxBorder"] : false;
+        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 5;
+        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : true;
+        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 3;
+        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
+        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
+        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 20;
+        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
+        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 255;
+        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 255;
+        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 255;
+        $BoxBorderAlpha = isset($Format["BoxBorderAlpha"]) ? $Format["BoxBorderAlpha"] : 100;
+        $NoMargin = isset($Format["NoMargin"]) ? $Format["NoMargin"] : false;
+
+        if (is_array($Value)) {
+            foreach ($Value as $Key => $ID) {
+                $this->drawThreshold($ID, $Format);
+            }
+            return 0;
+        }
+
+        $CaptionSettings = [
+            "DrawBox" => $DrawBox,
+            "DrawBoxBorder" => $DrawBoxBorder,
+            "BorderOffset" => $BorderOffset,
+            "BoxRounded" => $BoxRounded,
+            "RoundedRadius" => $RoundedRadius,
+            "BoxR" => $BoxR,
+            "BoxG" => $BoxG,
+            "BoxB" => $BoxB,
+            "BoxAlpha" => $BoxAlpha,
+            "BoxSurrounding" => $BoxSurrounding,
+            "BoxBorderR" => $BoxBorderR,
+            "BoxBorderG" => $BoxBorderG,
+            "BoxBorderB" => $BoxBorderB,
+            "BoxBorderAlpha" => $BoxBorderAlpha,
+            "R" => $CaptionR,
+            "G" => $CaptionG,
+            "B" => $CaptionB,
+            "Alpha" => $CaptionAlpha
+        ];
+
+        $Data = $this->DataSet->getData();
+        $AbscissaMargin = $this->getAbscissaMargin($Data);
+
+        if ($NoMargin) {
+            $AbscissaMargin = 0;
+        }
+        if (!isset($Data["Axis"][$AxisID])) {
+            return -1;
+        }
+        if ($Caption == null) {
+            $Caption = $Value;
+        }
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $YPos = $this->scaleComputeY($Value, ["AxisID" => $AxisID]);
+            if ($YPos >= $this->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"]
+                && $YPos <= $this->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"]
+            ) {
+                $X1 = $this->GraphAreaX1 + $AbscissaMargin;
+                $X2 = $this->GraphAreaX2 - $AbscissaMargin;
+
+                $this->drawLine(
+                    $X1,
+                    $YPos,
+                    $X2,
+                    $YPos,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $Ticks,
+                        "Weight" => $Weight
+                    ]
+                );
+
+                if ($Wide) {
+                    $this->drawLine(
+                        $X1,
+                        $YPos - 1,
+                        $X2,
+                        $YPos - 1,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                    $this->drawLine(
+                        $X1,
+                        $YPos + 1,
+                        $X2,
+                        $YPos + 1,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                }
+
+                if ($WriteCaption) {
+                    if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                        $X = $X1 + $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLELEFT;
+                    } else {
+                        $X = $X2 - $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_MIDDLERIGHT;
+                    }
+
+                    $this->drawText($X, $YPos, $Caption, $CaptionSettings);
+                }
+            }
+
+            return ["Y" => $YPos];
+        }
+
+        if ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+            $XPos = $this->scaleComputeY($Value, ["AxisID" => $AxisID]);
+            if ($XPos >= $this->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"]
+                    && $XPos <= $this->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"]
+            ) {
+                $Y1 = $this->GraphAreaY1 + $AbscissaMargin;
+                $Y2 = $this->GraphAreaY2 - $AbscissaMargin;
+
+                $this->drawLine(
+                    $XPos,
+                    $Y1,
+                    $XPos,
+                    $Y2,
+                    [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $Ticks,
+                        "Weight" => $Weight
+                    ]
+                );
+
+                if ($Wide) {
+                    $this->drawLine(
+                        $XPos - 1,
+                        $Y1,
+                        $XPos - 1,
+                        $Y2,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                    $this->drawLine(
+                        $XPos + 1,
+                        $Y1,
+                        $XPos + 1,
+                        $Y2,
+                        [
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha / $WideFactor,
+                            "Ticks" => $Ticks
+                        ]
+                    );
+                }
+
+                if ($WriteCaption) {
+                    if ($CaptionAlign == CAPTION_LEFT_TOP) {
+                        $Y = $Y1 + $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                    } else {
+                        $Y = $Y2 - $CaptionOffset;
+                        $CaptionSettings["Align"] = TEXT_ALIGN_BOTTOMMIDDLE;
+                    }
+
+                    $CaptionSettings["Align"] = TEXT_ALIGN_TOPMIDDLE;
+                    $this->drawText($XPos, $Y, $Caption, $CaptionSettings);
+                }
+            }
+
+            return ["Y" => $XPos];
+        }
+    }
+
+    /**
+     * Draw a threshold with the computed scale
+     * @param mixed $Value1
+     * @param mixed $Value2
+     * @param array $Format
+     * @return array|int|null
+     */
+    public function drawThresholdArea($Value1, $Value2, array $Format = [])
+    {
+        $AxisID = isset($Format["AxisID"]) ? $Format["AxisID"] : 0;
+        $R = isset($Format["R"]) ? $Format["R"] : 255;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 20;
+        $Border = isset($Format["Border"]) ? $Format["Border"] : true;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha + 20;
+        $BorderTicks = isset($Format["BorderTicks"]) ? $Format["BorderTicks"] : 2;
+        $AreaName = isset($Format["AreaName"]) ? $Format["AreaName"] : null;
+        $NameAngle = isset($Format["NameAngle"]) ? $Format["NameAngle"] : ZONE_NAME_ANGLE_AUTO;
+        $NameR = isset($Format["NameR"]) ? $Format["NameR"] : 255;
+        $NameG = isset($Format["NameG"]) ? $Format["NameG"] : 255;
+        $NameB = isset($Format["NameB"]) ? $Format["NameB"] : 255;
+        $NameAlpha = isset($Format["NameAlpha"]) ? $Format["NameAlpha"] : 100;
+        $DisableShadowOnArea = isset($Format["DisableShadowOnArea"]) ? $Format["DisableShadowOnArea"] : true;
+        $NoMargin = isset($Format["NoMargin"]) ? $Format["NoMargin"] : false;
+
+        if ($Value1 > $Value2) {
+            list($Value1, $Value2) = [$Value2, $Value1];
+        }
+
+        $RestoreShadow = $this->Shadow;
+        if ($DisableShadowOnArea && $this->Shadow) {
+            $this->Shadow = false;
+        }
+
+        if ($BorderAlpha > 100) {
+            $BorderAlpha = 100;
+        }
+
+        $Data = $this->DataSet->getData();
+        $AbscissaMargin = $this->getAbscissaMargin($Data);
+
+        if ($NoMargin) {
+            $AbscissaMargin = 0;
+        }
+        if (!isset($Data["Axis"][$AxisID])) {
+            return -1;
+        }
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $XPos1 = $this->GraphAreaX1 + $AbscissaMargin;
+            $XPos2 = $this->GraphAreaX2 - $AbscissaMargin;
+            $YPos1 = $this->scaleComputeY($Value1, ["AxisID" => $AxisID]);
+            $YPos2 = $this->scaleComputeY($Value2, ["AxisID" => $AxisID]);
+
+            if ($YPos1 < $this->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"]) {
+                $YPos1 = $this->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($YPos1 > $this->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"]) {
+                $YPos1 = $this->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($YPos2 < $this->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"]) {
+                $YPos2 = $this->GraphAreaY1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($YPos2 > $this->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"]) {
+                $YPos2 = $this->GraphAreaY2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+
+            $this->drawFilledRectangle(
+                $XPos1,
+                $YPos1,
+                $XPos2,
+                $YPos2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+            if ($Border) {
+                $this->drawLine(
+                    $XPos1,
+                    $YPos1,
+                    $XPos2,
+                    $YPos1,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->drawLine(
+                    $XPos1,
+                    $YPos2,
+                    $XPos2,
+                    $YPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($XPos2 - $XPos1) / 2 + $XPos1;
+                $YPos = ($YPos2 - $YPos1) / 2 + $YPos1;
+                $this->Shadow = $RestoreShadow;
+                $this->drawText(
+                    $XPos,
+                    $YPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => 0,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $this->Shadow = false;
+                }
+            }
+
+            $this->Shadow = $RestoreShadow;
+            return ["Y1" => $YPos1, "Y2" => $YPos2];
+        } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+            $YPos1 = $this->GraphAreaY1 + $AbscissaMargin;
+            $YPos2 = $this->GraphAreaY2 - $AbscissaMargin;
+            $XPos1 = $this->scaleComputeY($Value1, ["AxisID" => $AxisID]);
+            $XPos2 = $this->scaleComputeY($Value2, ["AxisID" => $AxisID]);
+
+            if ($XPos1 < $this->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"]) {
+                $XPos1 = $this->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($XPos1 > $this->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"]) {
+                $XPos1 = $this->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($XPos2 < $this->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"]) {
+                $XPos2 = $this->GraphAreaX1 + $Data["Axis"][$AxisID]["Margin"];
+            }
+            if ($XPos2 > $this->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"]) {
+                $XPos2 = $this->GraphAreaX2 - $Data["Axis"][$AxisID]["Margin"];
+            }
+
+            $this->drawFilledRectangle(
+                $XPos1,
+                $YPos1,
+                $XPos2,
+                $YPos2,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+            if ($Border) {
+                $this->drawLine(
+                    $XPos1,
+                    $YPos1,
+                    $XPos1,
+                    $YPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+                $this->drawLine(
+                    $XPos2,
+                    $YPos1,
+                    $XPos2,
+                    $YPos2,
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Ticks" => $BorderTicks
+                    ]
+                );
+            }
+
+            if ($AreaName != null) {
+                $XPos = ($YPos2 - $YPos1) / 2 + $YPos1;
+                $YPos = ($XPos2 - $XPos1) / 2 + $XPos1;
+
+                if ($NameAngle == ZONE_NAME_ANGLE_AUTO) {
+                    $TxtPos = $this->getTextBox(
+                        $XPos,
+                        $YPos,
+                        $this->FontName,
+                        $this->FontSize,
+                        0,
+                        $AreaName
+                    );
+                    $TxtWidth = $TxtPos[1]["X"] - $TxtPos[0]["X"];
+                    $NameAngle = 90;
+                    if (abs($XPos2 - $XPos1) > $TxtWidth) {
+                        $NameAngle = 0;
+                    }
+                }
+                $this->Shadow = $RestoreShadow;
+                $this->drawText(
+                    $YPos,
+                    $XPos,
+                    $AreaName,
+                    [
+                        "R" => $NameR,
+                        "G" => $NameG,
+                        "B" => $NameB,
+                        "Alpha" => $NameAlpha,
+                        "Angle" => $NameAngle,
+                        "Align" => TEXT_ALIGN_MIDDLEMIDDLE
+                    ]
+                );
+                if ($DisableShadowOnArea) {
+                    $this->Shadow = false;
+                }
+            }
+
+            $this->Shadow = $RestoreShadow;
+            return ["Y1" => $XPos1, "Y2" => $XPos2];
+        }
+    }
+
+    /**
+     * Draw a plot chart
+     * @param array $Format
+     */
+    public function drawPlotChart(array $Format = [])
+    {
+        $PlotSize = isset($Format["PlotSize"]) ? $Format["PlotSize"] : null;
+        $PlotBorder = isset($Format["PlotBorder"]) ? $Format["PlotBorder"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 50;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 50;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 50;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 30;
+        $BorderSize = isset($Format["BorderSize"]) ? $Format["BorderSize"] : 2;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 4;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                if (isset($Serie["Weight"])) {
+                    $SerieWeight = $Serie["Weight"] + 2;
+                } else {
+                    $SerieWeight = 2;
+                }
+                if ($PlotSize != null) {
+                    $SerieWeight = $PlotSize;
+                }
+
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = (int) $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($Surrounding != null) {
+                    $BorderR = $R + $Surrounding;
+                    $BorderG = $G + $Surrounding;
+                    $BorderB = $B + $Surrounding;
+                }
+                if (isset($Serie["Picture"])) {
+                    $Picture = $Serie["Picture"];
+                    list($PicWidth, $PicHeight, $PicType) = $this->getPicInfo($Picture);
+                } else {
+                    $Picture = null;
+                    $PicOffset = 0;
+                }
+
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Shape = $Serie["Shape"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                $PosArray = $this->scaleComputeY($Serie["Data"], ["AxisID" => $Serie["Axis"]]);
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    if ($Picture != null) {
+                        $PicOffset = $PicHeight / 2;
+                        $SerieWeight = 0;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X,
+                                $Y - $DisplayOffset - $SerieWeight - $BorderSize - $PicOffset,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($Y != VOID) {
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "CIRCLE",
+                                    floor($X) . "," . floor($Y) . "," . $SerieWeight,
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat(
+                                        $Serie["Data"][$Key],
+                                        $Mode,
+                                        $Format,
+                                        $Unit
+                                    )
+                                );
+                            }
+
+                            if ($Picture != null) {
+                                $this->drawFromPicture(
+                                    $PicType,
+                                    $Picture,
+                                    $X - $PicWidth / 2,
+                                    $Y - $PicHeight / 2
+                                );
+                            } else {
+                                $this->drawShape(
+                                    $X,
+                                    $Y,
+                                    $Shape,
+                                    $SerieWeight,
+                                    $PlotBorder,
+                                    $BorderSize,
+                                    $R,
+                                    $G,
+                                    $B,
+                                    $Alpha,
+                                    $BorderR,
+                                    $BorderG,
+                                    $BorderB,
+                                    $BorderAlpha
+                                );
+                            }
+                        }
+                        $X = $X + $XStep;
+                    }
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    if ($Picture != null) {
+                        $PicOffset = $PicWidth / 2;
+                        $SerieWeight = 0;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X + $DisplayOffset + $SerieWeight + $BorderSize + $PicOffset,
+                                $Y,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "Angle" => 270,
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($X != VOID) {
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "CIRCLE",
+                                    floor($X) . "," . floor($Y) . "," . $SerieWeight,
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+
+                            if ($Picture != null) {
+                                $this->drawFromPicture(
+                                    $PicType,
+                                    $Picture,
+                                    $X - $PicWidth / 2,
+                                    $Y - $PicHeight / 2
+                                );
+                            } else {
+                                $this->drawShape(
+                                    $X,
+                                    $Y,
+                                    $Shape,
+                                    $SerieWeight,
+                                    $PlotBorder,
+                                    $BorderSize,
+                                    $R,
+                                    $G,
+                                    $B,
+                                    $Alpha,
+                                    $BorderR,
+                                    $BorderG,
+                                    $BorderB,
+                                    $BorderAlpha
+                                );
+                            }
+                        }
+                        $Y = $Y + $YStep;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a spline chart
+     * @param array $Format
+     */
+    public function drawSplineChart(array $Format = [])
+    {
+        $BreakVoid = isset($Format["BreakVoid"]) ? $Format["BreakVoid"] : true;
+        $VoidTicks = isset($Format["VoidTicks"]) ? $Format["VoidTicks"] : 4;
+        $BreakR = isset($Format["BreakR"]) ? $Format["BreakR"] : null; // 234
+        $BreakG = isset($Format["BreakG"]) ? $Format["BreakG"] : null; // 55
+        $BreakB = isset($Format["BreakB"]) ? $Format["BreakB"] : null; // 26
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapPlotSize = isset($Format["ImageMapPlotSize"]) ? $Format["ImageMapPlotSize"] : 5;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                $Weight = $Serie["Weight"];
+
+                if ($BreakR == null) {
+                    $BreakSettings = [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks
+                    ];
+                } else {
+                    $BreakSettings = [
+                        "R" => $BreakR,
+                        "G" => $BreakG,
+                        "B" => $BreakB,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks,
+                        "Weight" => $Weight
+                    ];
+                }
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $WayPoints = [];
+                    $Force = $XStep / 5;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $LastX = 1;
+                    $LastY = 1;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X,
+                                $Y - $DisplayOffset,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($RecordImageMap && $Y != VOID) {
+                            $this->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . $ImageMapPlotSize,
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+
+                        if ($Y == VOID && $LastY != null) {
+                            $this->drawSpline(
+                                $WayPoints,
+                                [
+                                    "Force" => $Force,
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha,
+                                    "Ticks" => $Ticks,
+                                    "Weight" => $Weight
+                                ]
+                            );
+                            $WayPoints = [];
+                        }
+
+                        if ($Y != VOID && $LastY == null && $LastGoodY != null && !$BreakVoid) {
+                            $this->drawLine($LastGoodX, $LastGoodY, $X, $Y, $BreakSettings);
+                        }
+
+                        if ($Y != VOID) {
+                            $WayPoints[] = [$X, $Y];
+                        }
+                        if ($Y != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($Y == VOID) {
+                            $Y = null;
+                        }
+
+                        $LastX = $X;
+                        $LastY = $Y;
+                        $X = $X + $XStep;
+                    }
+                    $this->drawSpline(
+                        $WayPoints,
+                        [
+                            "Force" => $Force,
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha,
+                            "Ticks" => $Ticks,
+                            "Weight" => $Weight
+                        ]
+                    );
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $WayPoints = [];
+                    $Force = $YStep / 5;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $LastX = 1;
+                    $LastY = 1;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X + $DisplayOffset,
+                                $Y,
+                                $this->scaleFormat(
+                                    $Serie["Data"][$Key],
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                ),
+                                [
+                                    "Angle" => 270,
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($RecordImageMap && $X != VOID) {
+                            $this->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . $ImageMapPlotSize,
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+
+                        if ($X == VOID && $LastX != null) {
+                            $this->drawSpline(
+                                $WayPoints,
+                                [
+                                    "Force" => $Force,
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha,
+                                    "Ticks" => $Ticks,
+                                    "Weight" => $Weight
+                                ]
+                            );
+                            $WayPoints = [];
+                        }
+
+                        if ($X != VOID && $LastX == null && $LastGoodX != null && !$BreakVoid) {
+                            $this->drawLine($LastGoodX, $LastGoodY, $X, $Y, $BreakSettings);
+                        }
+
+                        if ($X != VOID) {
+                            $WayPoints[] = [$X, $Y];
+                        }
+                        if ($X != VOID) {
+                            $LastGoodX = $X;
+                            $LastGoodY = $Y;
+                        }
+                        if ($X == VOID) {
+                            $X = null;
+                        }
+
+                        $LastX = $X;
+                        $LastY = $Y;
+                        $Y = $Y + $YStep;
+                    }
+                    $this->drawSpline(
+                        $WayPoints,
+                        [
+                            "Force" => $Force,
+                            "R" => $R,
+                            "G" => $G,
+                            "B" => $B,
+                            "Alpha" => $Alpha,
+                            "Ticks" => $Ticks,
+                            "Weight" => $Weight
+                        ]
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a filled spline chart
+     * @param array $Format
+     */
+    public function drawFilledSplineChart(array $Format = [])
+    {
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $AroundZero = isset($Format["AroundZero"]) ? $Format["AroundZero"] : true;
+        $Threshold = isset($Format["Threshold"]) ? $Format["Threshold"] : null;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+                if ($AroundZero) {
+                    $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+                }
+
+                if ($Threshold != null) {
+                    foreach ($Threshold as $Key => $Params) {
+                        $Threshold[$Key]["MinX"] = $this->scaleComputeY(
+                            $Params["Min"],
+                            ["AxisID" => $Serie["Axis"]]
+                        );
+                        $Threshold[$Key]["MaxX"] = $this->scaleComputeY(
+                            $Params["Max"],
+                            ["AxisID" => $Serie["Axis"]]
+                        );
+                    }
+                }
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $WayPoints = [];
+                    $Force = $XStep / 5;
+
+                    if (!$AroundZero) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+                    if ($YZero < $this->GraphAreaY1 + 1) {
+                        $YZero = $this->GraphAreaY1 + 1;
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X,
+                                $Y - $DisplayOffset,
+                                $this->scaleFormat(
+                                    $Serie["Data"][$Key],
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                ),
+                                [
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($Y == VOID) {
+                            $Area = $this->drawSpline(
+                                $WayPoints,
+                                ["Force" => $Force, "PathOnly" => true]
+                            );
+
+                            if (count($Area)) {
+                                foreach ($Area as $key => $Points) {
+                                    $Corners = [];
+                                    $Corners[] = $Area[$key][0]["X"];
+                                    $Corners[] = $YZero;
+                                    foreach ($Points as $subKey => $Point) {
+                                        if ($subKey == count($Points) - 1) {
+                                            $Corners[] = $Point["X"] - 1;
+                                        } else {
+                                            $Corners[] = $Point["X"];
+                                        }
+                                        $Corners[] = $Point["Y"] + 1;
+                                    }
+                                    $Corners[] = $Points[$subKey]["X"] - 1;
+                                    $Corners[] = $YZero;
+
+                                    $this->drawPolygonChart(
+                                        $Corners,
+                                        [
+                                            "R" => $R,
+                                            "G" => $G,
+                                            "B" => $B,
+                                            "Alpha" => $Alpha / 2,
+                                            "NoBorder" => true,
+                                            "Threshold" => $Threshold
+                                        ]
+                                    );
+                                }
+                                $this->drawSpline(
+                                    $WayPoints,
+                                    [
+                                        "Force" => $Force,
+                                        "R" => $R,
+                                        "G" => $G,
+                                        "B" => $B,
+                                        "Alpha" => $Alpha,
+                                        "Ticks" => $Ticks
+                                    ]
+                                );
+                            }
+
+                            $WayPoints = [];
+                        } else {
+                            $WayPoints[] = [$X, $Y - .5]; /* -.5 for AA visual fix */
+                        }
+                        $X = $X + $XStep;
+                    }
+                    $Area = $this->drawSpline($WayPoints, ["Force" => $Force, "PathOnly" => true]);
+
+                    if (count($Area)) {
+                        foreach ($Area as $key => $Points) {
+                            $Corners = [];
+                            $Corners[] = $Area[$key][0]["X"];
+                            $Corners[] = $YZero;
+                            foreach ($Points as $subKey => $Point) {
+                                if ($subKey == count($Points) - 1) {
+                                    $Corners[] = $Point["X"] - 1;
+                                } else {
+                                    $Corners[] = $Point["X"];
+                                }
+                                $Corners[] = $Point["Y"] + 1;
+                            }
+                            $Corners[] = $Points[$subKey]["X"] - 1;
+                            $Corners[] = $YZero;
+
+                            $this->drawPolygonChart(
+                                $Corners,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha / 2,
+                                    "NoBorder" => true,
+                                    "Threshold" => $Threshold
+                                ]
+                            );
+                        }
+                        $this->drawSpline(
+                            $WayPoints,
+                            [
+                                "Force" => $Force,
+                                "R" => $R,
+                                "G" => $G,
+                                "B" => $B,
+                                "Alpha" => $Alpha,
+                                "Ticks" => $Ticks
+                            ]
+                        );
+                    }
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $WayPoints = [];
+                    $Force = $YStep / 5;
+
+                    if (!$AroundZero) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues) {
+                            $this->drawText(
+                                $X + $DisplayOffset,
+                                $Y,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "Angle" => 270,
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+                        if ($X == VOID) {
+                            $Area = $this->drawSpline(
+                                $WayPoints,
+                                ["Force" => $Force, "PathOnly" => true]
+                            );
+
+                            if (count($Area)) {
+                                foreach ($Area as $key => $Points) {
+                                    $Corners = [];
+                                    $Corners[] = $YZero;
+                                    $Corners[] = $Area[$key][0]["Y"];
+                                    foreach ($Points as $subKey => $Point) {
+                                        if ($subKey == count($Points) - 1) {
+                                            $Corners[] = $Point["X"] - 1;
+                                        } else {
+                                            $Corners[] = $Point["X"];
+                                        }
+                                        $Corners[] = $Point["Y"];
+                                    }
+                                    $Corners[] = $YZero;
+                                    $Corners[] = $Points[$subKey]["Y"] - 1;
+
+                                    $this->drawPolygonChart(
+                                        $Corners,
+                                        [
+                                            "R" => $R,
+                                            "G" => $G,
+                                            "B" => $B,
+                                            "Alpha" => $Alpha / 2,
+                                            "NoBorder" => true,
+                                            "Threshold" => $Threshold
+                                        ]
+                                    );
+                                }
+                                $this->drawSpline(
+                                    $WayPoints,
+                                    [
+                                        "Force" => $Force,
+                                        "R" => $R,
+                                        "G" => $G,
+                                        "B" => $B,
+                                        "Alpha" => $Alpha,
+                                        "Ticks" => $Ticks
+                                    ]
+                                );
+                            }
+
+                            $WayPoints = [];
+                        } else {
+                            $WayPoints[] = [$X, $Y];
+                        }
+                        $Y = $Y + $YStep;
+                    }
+                    $Area = $this->drawSpline(
+                        $WayPoints,
+                        ["Force" => $Force, "PathOnly" => true]
+                    );
+
+                    if (count($Area)) {
+                        foreach ($Area as $key => $Points) {
+                            $Corners = [];
+                            $Corners[] = $YZero;
+                            $Corners[] = $Area[$key][0]["Y"];
+                            foreach ($Points as $subKey => $Point) {
+                                if ($subKey == count($Points) - 1) {
+                                    $Corners[] = $Point["X"] - 1;
+                                } else {
+                                    $Corners[] = $Point["X"];
+                                }
+                                $Corners[] = $Point["Y"];
+                            }
+                            $Corners[] = $YZero;
+                            $Corners[] = $Points[$subKey]["Y"] - 1;
+
+                            $this->drawPolygonChart(
+                                $Corners,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha / 2,
+                                    "NoBorder" => true,
+                                    "Threshold" => $Threshold
+                                ]
+                            );
+                        }
+                        $this->drawSpline(
+                            $WayPoints,
+                            [
+                                "Force" => $Force,
+                                "R" => $R,
+                                "G" => $G,
+                                "B" => $B,
+                                "Alpha" => $Alpha,
+                                "Ticks" => $Ticks
+                            ]
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a line chart
+     * @param array $Format
+     */
+    public function drawLineChart(array $Format = [])
+    {
+        $BreakVoid = isset($Format["BreakVoid"]) ? $Format["BreakVoid"] : true;
+        $VoidTicks = isset($Format["VoidTicks"]) ? $Format["VoidTicks"] : 4;
+        $BreakR = isset($Format["BreakR"]) ? $Format["BreakR"] : null;
+        $BreakG = isset($Format["BreakG"]) ? $Format["BreakG"] : null;
+        $BreakB = isset($Format["BreakB"]) ? $Format["BreakB"] : null;
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapPlotSize = isset($Format["ImageMapPlotSize"]) ? $Format["ImageMapPlotSize"] : 5;
+        $ForceColor = isset($Format["ForceColor"]) ? $Format["ForceColor"] : false;
+        $ForceR = isset($Format["ForceR"]) ? $Format["ForceR"] : 0;
+        $ForceG = isset($Format["ForceG"]) ? $Format["ForceG"] : 0;
+        $ForceB = isset($Format["ForceB"]) ? $Format["ForceB"] : 0;
+        $ForceAlpha = isset($Format["ForceAlpha"]) ? $Format["ForceAlpha"] : 100;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                $Weight = $Serie["Weight"];
+
+                if ($ForceColor) {
+                    $R = $ForceR;
+                    $G = $ForceG;
+                    $B = $ForceB;
+                    $Alpha = $ForceAlpha;
+                }
+
+                if ($BreakR == null) {
+                    $BreakSettings = [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks,
+                        "Weight" => $Weight
+                    ];
+                } else {
+                    $BreakSettings = [
+                        "R" => $BreakR,
+                        "G" => $BreakG,
+                        "B" => $BreakB,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks,
+                        "Weight" => $Weight
+                    ];
+                }
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            if ($Serie["Data"][$Key] > 0) {
+                                $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                                $Offset = $DisplayOffset;
+                            } else {
+                                $Align = TEXT_ALIGN_TOPMIDDLE;
+                                $Offset = -$DisplayOffset;
+                            }
+                            $this->drawText(
+                                $X,
+                                $Y - $Offset - $Weight,
+                                $this->scaleFormat(
+                                    $Serie["Data"][$Key],
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                ),
+                                [
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => $Align
+                                ]
+                            );
+                        }
+
+                        if ($RecordImageMap && $Y != VOID) {
+                            $this->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . $ImageMapPlotSize,
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+
+                        if ($Y != VOID && $LastX != null && $LastY != null) {
+                            $this->drawLine(
+                                $LastX,
+                                $LastY,
+                                $X,
+                                $Y,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha,
+                                    "Ticks" => $Ticks,
+                                    "Weight" => $Weight
+                                ]
+                            );
+                        }
+                        if ($Y != VOID && $LastY == null && $LastGoodY != null && !$BreakVoid) {
+                            $this->drawLine(
+                                $LastGoodX,
+                                $LastGoodY,
+                                $X,
+                                $Y,
+                                $BreakSettings
+                            );
+                            $LastGoodY = null;
+                        }
+
+                        if ($Y != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($Y == VOID) {
+                            $Y = null;
+                        }
+
+                        $LastX = $X;
+                        $LastY = $Y;
+                        $X = $X + $XStep;
+                    }
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            $this->drawText(
+                                $X + $DisplayOffset + $Weight,
+                                $Y,
+                                $this->scaleFormat(
+                                    $Serie["Data"][$Key],
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                ),
+                                [
+                                    "Angle" => 270,
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => TEXT_ALIGN_BOTTOMMIDDLE
+                                ]
+                            );
+                        }
+
+                        if ($RecordImageMap && $X != VOID) {
+                            $this->addToImageMap(
+                                "CIRCLE",
+                                floor($X) . "," . floor($Y) . "," . $ImageMapPlotSize,
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+
+                        if ($X != VOID && $LastX != null && $LastY != null) {
+                            $this->drawLine(
+                                $LastX,
+                                $LastY,
+                                $X,
+                                $Y,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "Alpha" => $Alpha,
+                                    "Ticks" => $Ticks,
+                                    "Weight" => $Weight
+                                ]
+                            );
+                        }
+                        if ($X != VOID && $LastX == null && $LastGoodY != null && !$BreakVoid) {
+                            $this->drawLine(
+                                $LastGoodX,
+                                $LastGoodY,
+                                $X,
+                                $Y,
+                                $BreakSettings
+                            );
+                            $LastGoodY = null;
+                        }
+
+                        if ($X != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($X == VOID) {
+                            $X = null;
+                        }
+
+                        $LastX = $X;
+                        $LastY = $Y;
+                        $Y = $Y + $YStep;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a zone chart
+     *
+     * @param string $SerieA
+     * @param string $SerieB
+     * @param array $Format
+     * @return null|integer
+     */
+    public function drawZoneChart($SerieA, $SerieB, array $Format = [])
+    {
+        $AxisID = isset($Format["AxisID"]) ? $Format["AxisID"] : 0;
+        $LineR = isset($Format["LineR"]) ? $Format["LineR"] : 150;
+        $LineG = isset($Format["LineG"]) ? $Format["LineG"] : 150;
+        $LineB = isset($Format["LineB"]) ? $Format["LineB"] : 150;
+        $LineAlpha = isset($Format["LineAlpha"]) ? $Format["LineAlpha"] : 50;
+        $LineTicks = isset($Format["LineTicks"]) ? $Format["LineTicks"] : 1;
+        $AreaR = isset($Format["AreaR"]) ? $Format["AreaR"] : 150;
+        $AreaG = isset($Format["AreaG"]) ? $Format["AreaG"] : 150;
+        $AreaB = isset($Format["AreaB"]) ? $Format["AreaB"] : 150;
+        $AreaAlpha = isset($Format["AreaAlpha"]) ? $Format["AreaAlpha"] : 5;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        if (!isset($Data["Series"][$SerieA]["Data"])
+            || !isset($Data["Series"][$SerieB]["Data"])
+        ) {
+            return 0;
+        }
+        $SerieAData = $Data["Series"][$SerieA]["Data"];
+        $SerieBData = $Data["Series"][$SerieB]["Data"];
+
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        $Mode = $Data["Axis"][$AxisID]["Display"];
+        $Format = $Data["Axis"][$AxisID]["Format"];
+
+        $PosArrayA = $this->scaleComputeY($SerieAData, ["AxisID" => $AxisID]);
+        $PosArrayB = $this->scaleComputeY($SerieBData, ["AxisID" => $AxisID]);
+        if (count($PosArrayA) != count($PosArrayB)) {
+            return 0;
+        }
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            if ($XDivs == 0) {
+                $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+            } else {
+                $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+            }
+            $X = $this->GraphAreaX1 + $XMargin;
+            $LastX = null;
+            $LastY = null;
+
+            $LastY1 = null;
+            $LastY2 = null;
+            $BoundsA = [];
+            $BoundsB = [];
+            foreach ($PosArrayA as $Key => $Y1) {
+                $Y2 = $PosArrayB[$Key];
+
+                $BoundsA[] = $X;
+                $BoundsA[] = $Y1;
+                $BoundsB[] = $X;
+                $BoundsB[] = $Y2;
+
+                $LastX = $X;
+                $LastY1 = $Y1;
+                $LastY2 = $Y2;
+
+                $X = $X + $XStep;
+            }
+            $Bounds = array_merge($BoundsA, $this->reversePlots($BoundsB));
+            $this->drawPolygonChart(
+                $Bounds,
+                [
+                    "R" => $AreaR,
+                    "G" => $AreaG,
+                    "B" => $AreaB,
+                    "Alpha" => $AreaAlpha
+                ]
+            );
+
+            for ($i = 0; $i <= count($BoundsA) - 4; $i = $i + 2) {
+                $this->drawLine(
+                    $BoundsA[$i],
+                    $BoundsA[$i + 1],
+                    $BoundsA[$i + 2],
+                    $BoundsA[$i + 3],
+                    [
+                        "R" => $LineR,
+                        "G" => $LineG,
+                        "B" => $LineB,
+                        "Alpha" => $LineAlpha,
+                        "Ticks" => $LineTicks
+                    ]
+                );
+                $this->drawLine(
+                    $BoundsB[$i],
+                    $BoundsB[$i + 1],
+                    $BoundsB[$i + 2],
+                    $BoundsB[$i + 3],
+                    [
+                        "R" => $LineR,
+                        "G" => $LineG,
+                        "B" => $LineB,
+                        "Alpha" => $LineAlpha,
+                        "Ticks" => $LineTicks
+                    ]
+                );
+            }
+        } else {
+            if ($XDivs == 0) {
+                $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+            } else {
+                $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+            }
+            $Y = $this->GraphAreaY1 + $XMargin;
+            $LastX = null;
+            $LastY = null;
+
+            $LastX1 = null;
+            $LastX2 = null;
+            $BoundsA = [];
+            $BoundsB = [];
+            foreach ($PosArrayA as $Key => $X1) {
+                $X2 = $PosArrayB[$Key];
+
+                $BoundsA[] = $X1;
+                $BoundsA[] = $Y;
+                $BoundsB[] = $X2;
+                $BoundsB[] = $Y;
+
+                $LastY = $Y;
+                $LastX1 = $X1;
+                $LastX2 = $X2;
+
+                $Y = $Y + $YStep;
+            }
+            $Bounds = array_merge($BoundsA, $this->reversePlots($BoundsB));
+            $this->drawPolygonChart(
+                $Bounds,
+                ["R" => $AreaR, "G" => $AreaG, "B" => $AreaB, "Alpha" => $AreaAlpha]
+            );
+
+            for ($i = 0; $i <= count($BoundsA) - 4; $i = $i + 2) {
+                $this->drawLine(
+                    $BoundsA[$i],
+                    $BoundsA[$i + 1],
+                    $BoundsA[$i + 2],
+                    $BoundsA[$i + 3],
+                    [
+                        "R" => $LineR,
+                        "G" => $LineG,
+                        "B" => $LineB,
+                        "Alpha" => $LineAlpha,
+                        "Ticks" => $LineTicks
+                    ]
+                );
+                $this->drawLine(
+                    $BoundsB[$i],
+                    $BoundsB[$i + 1],
+                    $BoundsB[$i + 2],
+                    $BoundsB[$i + 3],
+                    [
+                        "R" => $LineR,
+                        "G" => $LineG,
+                        "B" => $LineB,
+                        "Alpha" => $LineAlpha,
+                        "Ticks" => $LineTicks
+                    ]
+                );
+            }
+        }
+    }
+
+    /**
+     * Draw a step chart
+     * @param array $Format
+     */
+    public function drawStepChart(array $Format = [])
+    {
+        $BreakVoid = isset($Format["BreakVoid"]) ? $Format["BreakVoid"] : false;
+        $ReCenter = isset($Format["ReCenter"]) ? $Format["ReCenter"] : true;
+        $VoidTicks = isset($Format["VoidTicks"]) ? $Format["VoidTicks"] : 4;
+        $BreakR = isset($Format["BreakR"]) ? $Format["BreakR"] : null;
+        $BreakG = isset($Format["BreakG"]) ? $Format["BreakG"] : null;
+        $BreakB = isset($Format["BreakB"]) ? $Format["BreakB"] : null;
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $ImageMapPlotSize = isset($Format["ImageMapPlotSize"]) ? $Format["ImageMapPlotSize"] : 5;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                $Weight = $Serie["Weight"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                if ($BreakR == null) {
+                    $BreakSettings = [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks,
+                        "Weight" => $Weight
+                    ];
+                } else {
+                    $BreakSettings = [
+                        "R" => $BreakR,
+                        "G" => $BreakG,
+                        "B" => $BreakB,
+                        "Alpha" => $Alpha,
+                        "Ticks" => $VoidTicks,
+                        "Weight" => $Weight
+                    ];
+                }
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+                $Color = [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "Ticks" => $Ticks,
+                    "Weight" => $Weight
+                ];
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $Init = false;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            if ($Y <= $LastY) {
+                                $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                                $Offset = $DisplayOffset;
+                            } else {
+                                $Align = TEXT_ALIGN_TOPMIDDLE;
+                                $Offset = -$DisplayOffset;
+                            }
+                            $this->drawText(
+                                $X,
+                                $Y - $Offset - $Weight,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                ["R" => $DisplayR, "G" => $DisplayG, "B" => $DisplayB, "Align" => $Align]
+                            );
+                        }
+
+                        if ($Y != VOID && $LastX != null && $LastY != null) {
+                            $this->drawLine($LastX, $LastY, $X, $LastY, $Color);
+                            $this->drawLine($X, $LastY, $X, $Y, $Color);
+                            if ($ReCenter && $X + $XStep < $this->GraphAreaX2 - $XMargin) {
+                                $this->drawLine($X, $Y, $X + $XStep, $Y, $Color);
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            '%s,%s,%s,%s',
+                                            floor($X - $ImageMapPlotSize),
+                                            floor($Y - $ImageMapPlotSize),
+                                            floor($X + $XStep + $ImageMapPlotSize),
+                                            floor($Y + $ImageMapPlotSize)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            } else {
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            '%s,%s,%s,%s',
+                                            floor($LastX - $ImageMapPlotSize),
+                                            floor($LastY - $ImageMapPlotSize),
+                                            floor($X + $ImageMapPlotSize),
+                                            floor($LastY + $ImageMapPlotSize)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            }
+                        }
+
+                        if ($Y != VOID && $LastY == null && $LastGoodY != null && !$BreakVoid) {
+                            if ($ReCenter) {
+                                $this->drawLine($LastGoodX + $XStep, $LastGoodY, $X, $LastGoodY, $BreakSettings);
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            '%s,%s,%s,%s',
+                                            floor($LastGoodX + $XStep - $ImageMapPlotSize),
+                                            floor($LastGoodY - $ImageMapPlotSize),
+                                            floor($X + $ImageMapPlotSize),
+                                            floor($LastGoodY + $ImageMapPlotSize)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            } else {
+                                $this->drawLine($LastGoodX, $LastGoodY, $X, $LastGoodY, $BreakSettings);
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            '%s,%s,%s,%s',
+                                            floor($LastGoodX - $ImageMapPlotSize),
+                                            floor($LastGoodY - $ImageMapPlotSize),
+                                            floor($X + $ImageMapPlotSize),
+                                            floor($LastGoodY + $ImageMapPlotSize)
+                                        ),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            }
+
+                            $this->drawLine($X, $LastGoodY, $X, $Y, $BreakSettings);
+                            $LastGoodY = null;
+                        } elseif (!$BreakVoid && $LastGoodY == null && $Y != VOID) {
+                            $this->drawLine($this->GraphAreaX1 + $XMargin, $Y, $X, $Y, $BreakSettings);
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        '%s,%s,%s,%s',
+                                        floor($this->GraphAreaX1 + $XMargin - $ImageMapPlotSize),
+                                        floor($Y - $ImageMapPlotSize),
+                                        floor($X + $ImageMapPlotSize),
+                                        floor($Y + $ImageMapPlotSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+                        }
+
+                        if ($Y != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($Y == VOID) {
+                            $Y = null;
+                        }
+
+                        if (!$Init && $ReCenter) {
+                            $X = $X - $XStep / 2;
+                            $Init = true;
+                        }
+                        $LastX = $X;
+                        $LastY = $Y;
+                        if ($LastX < $this->GraphAreaX1 + $XMargin) {
+                            $LastX = $this->GraphAreaX1 + $XMargin;
+                        }
+                        $X = $X + $XStep;
+                    }
+                    if ($ReCenter) {
+                        $this->drawLine($LastX, $LastY, $this->GraphAreaX2 - $XMargin, $LastY, $Color);
+                        if ($RecordImageMap) {
+                            $this->addToImageMap(
+                                "RECT",
+                                sprintf(
+                                    '%s,%s,%s,%s',
+                                    floor($LastX - $ImageMapPlotSize),
+                                    floor($LastY - $ImageMapPlotSize),
+                                    floor($this->GraphAreaX2 - $XMargin + $ImageMapPlotSize),
+                                    floor($LastY + $ImageMapPlotSize)
+                                ),
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+                    }
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $Init = false;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            if ($X >= $LastX) {
+                                $Align = TEXT_ALIGN_MIDDLELEFT;
+                                $Offset = $DisplayOffset;
+                            } else {
+                                $Align = TEXT_ALIGN_MIDDLERIGHT;
+                                $Offset = -$DisplayOffset;
+                            }
+                            $this->drawText(
+                                $X + $Offset + $Weight,
+                                $Y,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => $Align
+                                ]
+                            );
+                        }
+
+                        if ($X != VOID && $LastX != null && $LastY != null) {
+                            $this->drawLine($LastX, $LastY, $LastX, $Y, $Color);
+                            $this->drawLine($LastX, $Y, $X, $Y, $Color);
+
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        '%s,%s,%s,%s',
+                                        floor($LastX - $ImageMapPlotSize),
+                                        floor($LastY - $ImageMapPlotSize),
+                                        floor($LastX + $XStep + $ImageMapPlotSize),
+                                        floor($Y + $ImageMapPlotSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+                        }
+
+                        if ($X != VOID && $LastX == null && $LastGoodY != null && !$BreakVoid) {
+                            $this->drawLine(
+                                $LastGoodX,
+                                $LastGoodY,
+                                $LastGoodX,
+                                $LastGoodY + $YStep,
+                                $Color
+                            );
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        '%s,%s,%s,%s',
+                                        floor($LastGoodX - $ImageMapPlotSize),
+                                        floor($LastGoodY - $ImageMapPlotSize),
+                                        floor($LastGoodX + $ImageMapPlotSize),
+                                        floor($LastGoodY + $YStep + $ImageMapPlotSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+
+                            $this->drawLine(
+                                $LastGoodX,
+                                $LastGoodY + $YStep,
+                                $LastGoodX,
+                                $Y,
+                                $BreakSettings
+                            );
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        '%s,%s,%s,%s',
+                                        floor($LastGoodX - $ImageMapPlotSize),
+                                        floor($LastGoodY + $YStep - $ImageMapPlotSize),
+                                        floor($LastGoodX + $ImageMapPlotSize),
+                                        floor($YStep + $ImageMapPlotSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+
+                            $this->drawLine($LastGoodX, $Y, $X, $Y, $BreakSettings);
+                            $LastGoodY = null;
+                        } elseif ($X != VOID && $LastGoodY == null && !$BreakVoid) {
+                            $this->drawLine($X, $this->GraphAreaY1 + $XMargin, $X, $Y, $BreakSettings);
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        '%s,%s,%s,%s',
+                                        floor($X - $ImageMapPlotSize),
+                                        floor($this->GraphAreaY1 + $XMargin - $ImageMapPlotSize),
+                                        floor($X + $ImageMapPlotSize),
+                                        floor($Y + $ImageMapPlotSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+                        }
+
+                        if ($X != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($X == VOID) {
+                            $X = null;
+                        }
+
+                        if (!$Init && $ReCenter) {
+                            $Y = $Y - $YStep / 2;
+                            $Init = true;
+                        }
+                        $LastX = $X;
+                        $LastY = $Y;
+                        if ($LastY < $this->GraphAreaY1 + $XMargin) {
+                            $LastY = $this->GraphAreaY1 + $XMargin;
+                        }
+                        $Y = $Y + $YStep;
+                    }
+                    if ($ReCenter) {
+                        $this->drawLine($LastX, $LastY, $LastX, $this->GraphAreaY2 - $XMargin, $Color);
+                        if ($RecordImageMap) {
+                            $this->addToImageMap(
+                                "RECT",
+                                sprintf(
+                                    '%s,%s,%s,%s',
+                                    floor($LastX - $ImageMapPlotSize),
+                                    floor($LastY - $ImageMapPlotSize),
+                                    floor($LastX + $ImageMapPlotSize),
+                                    floor($this->GraphAreaY2 - $XMargin + $ImageMapPlotSize)
+                                ),
+                                $this->toHTMLColor($R, $G, $B),
+                                $SerieDescription,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a step chart
+     * @param array $Format
+     */
+    public function drawFilledStepChart(array $Format = [])
+    {
+        $ReCenter = isset($Format["ReCenter"]) ? $Format["ReCenter"] : true;
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $ForceTransparency = isset($Format["ForceTransparency"]) ? $Format["ForceTransparency"] : null;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $AroundZero = isset($Format["AroundZero"]) ? $Format["AroundZero"] : true;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+
+                $Color = ["R" => $R, "G" => $G, "B" => $B];
+                if ($ForceTransparency != null) {
+                    $Color["Alpha"] = $ForceTransparency;
+                } else {
+                    $Color["Alpha"] = $Alpha;
+                }
+
+                $PosArray = $this->scaleComputeY($Serie["Data"], ["AxisID" => $Serie["Axis"]]);
+                $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+                    if ($YZero < $this->GraphAreaY1 + 1) {
+                        $YZero = $this->GraphAreaY1 + 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!$AroundZero) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $Points = [];
+                    $Init = false;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($Y == VOID && $LastX != null && $LastY != null && count($Points)) {
+                            $Points[] = $LastX;
+                            $Points[] = $LastY;
+                            $Points[] = $X;
+                            $Points[] = $LastY;
+                            $Points[] = $X;
+                            $Points[] = $YZero;
+                            $this->drawPolygon($Points, $Color);
+                            $Points = [];
+                        }
+
+                        if ($Y != VOID && $LastX != null && $LastY != null) {
+                            if (count($Points)) {
+                                $Points[] = $LastX;
+                                $Points[] = $YZero;
+                            }
+                            $Points[] = $LastX;
+                            $Points[] = $LastY;
+                            $Points[] = $X;
+                            $Points[] = $LastY;
+                            $Points[] = $X;
+                            $Points[] = $Y;
+                        }
+
+                        if ($Y != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($Y == VOID) {
+                            $Y = null;
+                        }
+
+                        if (!$Init && $ReCenter) {
+                            $X = $X - $XStep / 2;
+                            $Init = true;
+                        }
+                        $LastX = $X;
+                        $LastY = $Y;
+                        if ($LastX < $this->GraphAreaX1 + $XMargin) {
+                            $LastX = $this->GraphAreaX1 + $XMargin;
+                        }
+                        $X = $X + $XStep;
+                    }
+
+                    if ($ReCenter) {
+                        $Points[] = $LastX + $XStep / 2;
+                        $Points[] = $LastY;
+                        $Points[] = $LastX + $XStep / 2;
+                        $Points[] = $YZero;
+                    } else {
+                        $Points[] = $LastX;
+                        $Points[] = $YZero;
+                    }
+
+                    $this->drawPolygon($Points, $Color);
+                } else {
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $LastGoodY = null;
+                    $LastGoodX = null;
+                    $Points = [];
+                    foreach ($PosArray as $Key => $X) {
+                        if ($X == VOID && $LastX != null && $LastY != null && count($Points)) {
+                            $Points[] = $LastX;
+                            $Points[] = $LastY;
+                            $Points[] = $LastX;
+                            $Points[] = $Y;
+                            $Points[] = $YZero;
+                            $Points[] = $Y;
+                            $this->drawPolygon($Points, $Color);
+                            $Points = [];
+                        }
+
+                        if ($X != VOID && $LastX != null && $LastY != null) {
+                            if (count($Points)) {
+                                $Points[] = $YZero;
+                                $Points[] = $LastY;
+                            }
+                            $Points[] = $LastX;
+                            $Points[] = $LastY;
+                            $Points[] = $LastX;
+                            $Points[] = $Y;
+                            $Points[] = $X;
+                            $Points[] = $Y;
+                        }
+
+                        if ($X != VOID) {
+                            $LastGoodY = $Y;
+                            $LastGoodX = $X;
+                        }
+                        if ($X == VOID) {
+                            $X = null;
+                        }
+
+                        if ($LastX == null && $ReCenter) {
+                            $Y = $Y - $YStep / 2;
+                        }
+                        $LastX = $X;
+                        $LastY = $Y;
+                        if ($LastY < $this->GraphAreaY1 + $XMargin) {
+                            $LastY = $this->GraphAreaY1 + $XMargin;
+                        }
+                        $Y = $Y + $YStep;
+                    }
+
+                    if ($ReCenter) {
+                        $Points[] = $LastX;
+                        $Points[] = $LastY + $YStep / 2;
+                        $Points[] = $YZero;
+                        $Points[] = $LastY + $YStep / 2;
+                    } else {
+                        $Points[] = $YZero;
+                        $Points[] = $LastY;
+                    }
+
+                    $this->drawPolygon($Points, $Color);
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw an area chart
+     * @param array $Format
+     */
+    public function drawAreaChart(array $Format = [])
+    {
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $ForceTransparency = isset($Format["ForceTransparency"]) ? $Format["ForceTransparency"] : 25;
+        $AroundZero = isset($Format["AroundZero"]) ? $Format["AroundZero"] : true;
+        $Threshold = isset($Format["Threshold"]) ? $Format["Threshold"] : null;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                $PosArray = $this->scaleComputeY($Serie["Data"], ["AxisID" => $Serie["Axis"]]);
+                $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+
+                if ($Threshold != null) {
+                    foreach ($Threshold as $Key => $Params) {
+                        $Threshold[$Key]["MinX"] = $this->scaleComputeY(
+                            $Params["Min"],
+                            ["AxisID" => $Serie["Axis"]]
+                        );
+                        $Threshold[$Key]["MaxX"] = $this->scaleComputeY(
+                            $Params["Max"],
+                            ["AxisID" => $Serie["Axis"]]
+                        );
+                    }
+                }
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+
+                    $Areas = [];
+                    $AreaID = 0;
+                    $Areas[$AreaID][] = $this->GraphAreaX1 + $XMargin;
+                    if ($AroundZero) {
+                        $Areas[$AreaID][] = $YZero;
+                    } else {
+                        $Areas[$AreaID][] = $this->GraphAreaY2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            if ($Serie["Data"][$Key] > 0) {
+                                $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                                $Offset = $DisplayOffset;
+                            } else {
+                                $Align = TEXT_ALIGN_TOPMIDDLE;
+                                $Offset = -$DisplayOffset;
+                            }
+                            $this->drawText(
+                                $X,
+                                $Y - $Offset,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                ["R" => $DisplayR, "G" => $DisplayG, "B" => $DisplayB, "Align" => $Align]
+                            );
+                        }
+
+                        if ($Y == VOID && isset($Areas[$AreaID])) {
+                            if ($LastX == null) {
+                                $Areas[$AreaID][] = $X;
+                            } else {
+                                $Areas[$AreaID][] = $LastX;
+                            }
+
+                            if ($AroundZero) {
+                                $Areas[$AreaID][] = $YZero;
+                            } else {
+                                $Areas[$AreaID][] = $this->GraphAreaY2 - 1;
+                            }
+                            $AreaID++;
+                        } elseif ($Y != VOID) {
+                            if (!isset($Areas[$AreaID])) {
+                                $Areas[$AreaID][] = $X;
+                                if ($AroundZero) {
+                                    $Areas[$AreaID][] = $YZero;
+                                } else {
+                                    $Areas[$AreaID][] = $this->GraphAreaY2 - 1;
+                                }
+                            }
+
+                            $Areas[$AreaID][] = $X;
+                            $Areas[$AreaID][] = $Y;
+                        }
+
+                        $LastX = $X;
+                        $X = $X + $XStep;
+                    }
+                    $Areas[$AreaID][] = $LastX;
+                    if ($AroundZero) {
+                        $Areas[$AreaID][] = $YZero;
+                    } else {
+                        $Areas[$AreaID][] = $this->GraphAreaY2 - 1;
+                    }
+
+                    /* Handle shadows in the areas */
+                    if ($this->Shadow) {
+                        $ShadowArea = [];
+                        foreach ($Areas as $Key => $Points) {
+                            $ShadowArea[$Key] = [];
+                            foreach ($Points as $Key2 => $Value) {
+                                if ($Key2 % 2 == 0) {
+                                    $ShadowArea[$Key][] = $Value + $this->ShadowX;
+                                } else {
+                                    $ShadowArea[$Key][] = $Value + $this->ShadowY;
+                                }
+                            }
+                        }
+
+                        foreach ($ShadowArea as $Key => $Points) {
+                            $this->drawPolygonChart(
+                                $Points,
+                                [
+                                    "R" => $this->ShadowR,
+                                    "G" => $this->ShadowG,
+                                    "B" => $this->ShadowB,
+                                    "Alpha" => $this->Shadowa
+                                ]
+                            );
+                        }
+                    }
+
+                    $Alpha = $ForceTransparency != null ? $ForceTransparency : $Alpha;
+                    $Color = [
+                        "R" => $R,
+                        "G" => $G,
+                        "B" => $B,
+                        "Alpha" => $Alpha,
+                        "Threshold" => $Threshold
+                    ];
+
+                    foreach ($Areas as $Key => $Points) {
+                        $this->drawPolygonChart($Points, $Color);
+                    }
+                } else {
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+
+                    $Areas = [];
+                    $AreaID = 0;
+                    if ($AroundZero) {
+                        $Areas[$AreaID][] = $YZero;
+                    } else {
+                        $Areas[$AreaID][] = $this->GraphAreaX1 + 1;
+                    }
+                    $Areas[$AreaID][] = $this->GraphAreaY1 + $XMargin;
+
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+                    $LastX = null;
+                    $LastY = null;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $X) {
+                        if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                            if ($Serie["Data"][$Key] > 0) {
+                                $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                                $Offset = $DisplayOffset;
+                            } else {
+                                $Align = TEXT_ALIGN_TOPMIDDLE;
+                                $Offset = -$DisplayOffset;
+                            }
+                            $this->drawText(
+                                $X + $Offset,
+                                $Y,
+                                $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                [
+                                    "Angle" => 270,
+                                    "R" => $DisplayR,
+                                    "G" => $DisplayG,
+                                    "B" => $DisplayB,
+                                    "Align" => $Align
+                                ]
+                            );
+                        }
+
+                        if ($X == VOID && isset($Areas[$AreaID])) {
+                            if ($AroundZero) {
+                                $Areas[$AreaID][] = $YZero;
+                            } else {
+                                $Areas[$AreaID][] = $this->GraphAreaX1 + 1;
+                            }
+
+                            if ($LastY == null) {
+                                $Areas[$AreaID][] = $Y;
+                            } else {
+                                $Areas[$AreaID][] = $LastY;
+                            }
+
+                            $AreaID++;
+                        } elseif ($X != VOID) {
+                            if (!isset($Areas[$AreaID])) {
+                                if ($AroundZero) {
+                                    $Areas[$AreaID][] = $YZero;
+                                } else {
+                                    $Areas[$AreaID][] = $this->GraphAreaX1 + 1;
+                                }
+                                $Areas[$AreaID][] = $Y;
+                            }
+
+                            $Areas[$AreaID][] = $X;
+                            $Areas[$AreaID][] = $Y;
+                        }
+
+                        $LastX = $X;
+                        $LastY = $Y;
+                        $Y = $Y + $YStep;
+                    }
+                    if ($AroundZero) {
+                        $Areas[$AreaID][] = $YZero;
+                    } else {
+                        $Areas[$AreaID][] = $this->GraphAreaX1 + 1;
+                    }
+                    $Areas[$AreaID][] = $LastY;
+
+                    /* Handle shadows in the areas */
+                    if ($this->Shadow) {
+                        $ShadowArea = [];
+                        foreach ($Areas as $Key => $Points) {
+                            $ShadowArea[$Key] = [];
+                            foreach ($Points as $Key2 => $Value) {
+                                if ($Key2 % 2 == 0) {
+                                    $ShadowArea[$Key][] = $Value + $this->ShadowX;
+                                } else {
+                                    $ShadowArea[$Key][] = $Value + $this->ShadowY;
+                                }
+                            }
+                        }
+
+                        foreach ($ShadowArea as $Key => $Points) {
+                            $this->drawPolygonChart(
+                                $Points,
+                                [
+                                    "R" => $this->ShadowR,
+                                    "G" => $this->ShadowG,
+                                    "B" => $this->ShadowB,
+                                    "Alpha" => $this->Shadowa
+                                ]
+                            );
+                        }
+                    }
+
+                    $Alpha = $ForceTransparency != null ? $ForceTransparency : $Alpha;
+                    $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Threshold" => $Threshold];
+
+                    foreach ($Areas as $Key => $Points) {
+                        $this->drawPolygonChart($Points, $Color);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a bar chart
+     * @param array $Format
+     */
+    public function drawBarChart(array $Format = [])
+    {
+        $Floating0Serie = isset($Format["Floating0Serie"]) ? $Format["Floating0Serie"] : null;
+        $Floating0Value = isset($Format["Floating0Value"]) ? $Format["Floating0Value"] : null;
+        $Draw0Line = isset($Format["Draw0Line"]) ? $Format["Draw0Line"] : false;
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 2;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayFont = isset($Format["DisplayFont"]) ? $Format["DisplayFont"] : $this->FontName;
+        $DisplaySize = isset($Format["DisplaySize"]) ? $Format["DisplaySize"] : $this->FontSize;
+        $DisplayPos = isset($Format["DisplayPos"]) ? $Format["DisplayPos"] : LABEL_POS_OUTSIDE;
+        $DisplayShadow = isset($Format["DisplayShadow"]) ? $Format["DisplayShadow"] : true;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $AroundZero = isset($Format["AroundZero"]) ? $Format["AroundZero"] : true;
+        $Interleave = isset($Format["Interleave"]) ? $Format["Interleave"] : .5;
+        $Rounded = isset($Format["Rounded"]) ? $Format["Rounded"] : false;
+        $RoundRadius = isset($Format["RoundRadius"]) ? $Format["RoundRadius"] : 4;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : -1;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : -1;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : -1;
+        $Gradient = isset($Format["Gradient"]) ? $Format["Gradient"] : false;
+        $GradientMode = isset($Format["GradientMode"]) ? $Format["GradientMode"] : GRADIENT_SIMPLE;
+        $GradientAlpha = isset($Format["GradientAlpha"]) ? $Format["GradientAlpha"] : 20;
+        $GradientStartR = isset($Format["GradientStartR"]) ? $Format["GradientStartR"] : 255;
+        $GradientStartG = isset($Format["GradientStartG"]) ? $Format["GradientStartG"] : 255;
+        $GradientStartB = isset($Format["GradientStartB"]) ? $Format["GradientStartB"] : 255;
+        $GradientEndR = isset($Format["GradientEndR"]) ? $Format["GradientEndR"] : 0;
+        $GradientEndG = isset($Format["GradientEndG"]) ? $Format["GradientEndG"] : 0;
+        $GradientEndB = isset($Format["GradientEndB"]) ? $Format["GradientEndB"] : 0;
+        $TxtMargin = isset($Format["TxtMargin"]) ? $Format["TxtMargin"] : 6;
+        $OverrideColors = isset($Format["OverrideColors"]) ? $Format["OverrideColors"] : null;
+        $OverrideSurrounding = isset($Format["OverrideSurrounding"]) ? $Format["OverrideSurrounding"] : 30;
+        $InnerSurrounding = isset($Format["InnerSurrounding"]) ? $Format["InnerSurrounding"] : null;
+        $InnerBorderR = isset($Format["InnerBorderR"]) ? $Format["InnerBorderR"] : -1;
+        $InnerBorderG = isset($Format["InnerBorderG"]) ? $Format["InnerBorderG"] : -1;
+        $InnerBorderB = isset($Format["InnerBorderB"]) ? $Format["InnerBorderB"] : -1;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        if ($OverrideColors != null) {
+            $OverrideColors = $this->validatePalette($OverrideColors, $OverrideSurrounding);
+            $this->DataSet->saveExtendedData("Palette", $OverrideColors);
+        }
+
+        $RestoreShadow = $this->Shadow;
+
+        $SeriesCount = $this->countDrawableSeries();
+        $CurrentSerie = 0;
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = $R;
+                    $DisplayG = $G;
+                    $DisplayB = $B;
+                }
+                if ($Surrounding != null) {
+                    $BorderR = $R + $Surrounding;
+                    $BorderG = $G + $Surrounding;
+                    $BorderB = $B + $Surrounding;
+                }
+                if ($InnerSurrounding != null) {
+                    $InnerBorderR = $R + $InnerSurrounding;
+                    $InnerBorderG = $G + $InnerSurrounding;
+                    $InnerBorderB = $B + $InnerSurrounding;
+                }
+                if ($InnerBorderR == -1) {
+                    $InnerColor = null;
+                } else {
+                    $InnerColor = [
+                        "R" => $InnerBorderR,
+                        "G" => $InnerBorderG,
+                        "B" => $InnerBorderB
+                    ];
+                }
+                $Color = [
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ];
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                if ($Floating0Value != null) {
+                    $YZero = $this->scaleComputeY(
+                        $Floating0Value,
+                        ["AxisID" => $Serie["Axis"]]
+                    );
+                } else {
+                    $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+                }
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+                    if ($YZero < $this->GraphAreaY1 + 1) {
+                        $YZero = $this->GraphAreaY1 + 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = 0;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    if ($AroundZero) {
+                        $Y1 = $YZero;
+                    } else {
+                        $Y1 = $this->GraphAreaY2 - 1;
+                    }
+                    if ($XDivs == 0) {
+                        $XSize = ($this->GraphAreaX2 - $this->GraphAreaX1) / ($SeriesCount + $Interleave);
+                    } else {
+                        $XSize = ($XStep / ($SeriesCount + $Interleave));
+                    }
+
+                    $XOffset = -($XSize * $SeriesCount) / 2 + $CurrentSerie * $XSize;
+                    if ($X + $XOffset <= $this->GraphAreaX1) {
+                        $XOffset = $this->GraphAreaX1 - $X + 1;
+                    }
+
+                    $this->DataSet->Data["Series"][$SerieName]["XOffset"] = $XOffset + $XSize / 2;
+
+                    if ($Rounded || $BorderR != -1) {
+                        $XSpace = 1;
+                    } else {
+                        $XSpace = 0;
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $ID = 0;
+                    foreach ($PosArray as $Key => $Y2) {
+                        if ($Floating0Serie != null) {
+                            if (isset($Data["Series"][$Floating0Serie]["Data"][$Key])) {
+                                $Value = $Data["Series"][$Floating0Serie]["Data"][$Key];
+                            } else {
+                                $Value = 0;
+                            }
+
+                            $YZero = $this->scaleComputeY($Value, ["AxisID" => $Serie["Axis"]]);
+                            if ($YZero > $this->GraphAreaY2 - 1) {
+                                $YZero = $this->GraphAreaY2 - 1;
+                            }
+                            if ($YZero < $this->GraphAreaY1 + 1) {
+                                $YZero = $this->GraphAreaY1 + 1;
+                            }
+
+                            if ($AroundZero) {
+                                $Y1 = $YZero;
+                            } else {
+                                $Y1 = $this->GraphAreaY2 - 1;
+                            }
+                        }
+
+                        if ($OverrideColors != null) {
+                            if (isset($OverrideColors[$ID])) {
+                                $Color = [
+                                    "R" => $OverrideColors[$ID]["R"],
+                                    "G" => $OverrideColors[$ID]["G"],
+                                    "B" => $OverrideColors[$ID]["B"],
+                                    "Alpha" => $OverrideColors[$ID]["Alpha"],
+                                    "BorderR" => $OverrideColors[$ID]["BorderR"],
+                                    "BorderG" => $OverrideColors[$ID]["BorderG"],
+                                    "BorderB" => $OverrideColors[$ID]["BorderB"]
+                                ];
+                            } else {
+                                $Color = $this->getRandomColor();
+                            }
+                        }
+
+                        if ($Y2 != VOID) {
+                            $BarHeight = $Y1 - $Y2;
+
+                            if ($Serie["Data"][$Key] == 0) {
+                                $this->drawLine(
+                                    $X + $XOffset + $XSpace,
+                                    $Y1,
+                                    $X + $XOffset + $XSize - $XSpace,
+                                    $Y1,
+                                    $Color
+                                );
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            "%s,%s,%s,%s",
+                                            floor($X + $XOffset + $XSpace),
+                                            floor($Y1 - 1),
+                                            floor($X + $XOffset + $XSize - $XSpace),
+                                            floor($Y1 + 1)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            } else {
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            "%s,%s,%s,%s",
+                                            floor($X + $XOffset + $XSpace),
+                                            floor($Y1),
+                                            floor($X + $XOffset + $XSize - $XSpace),
+                                            floor($Y2)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+
+                                if ($Rounded) {
+                                    $this->drawRoundedFilledRectangle(
+                                        $X + $XOffset + $XSpace,
+                                        $Y1,
+                                        $X + $XOffset + $XSize - $XSpace,
+                                        $Y2,
+                                        $RoundRadius,
+                                        $Color
+                                    );
+                                } else {
+                                    $this->drawFilledRectangle(
+                                        $X + $XOffset + $XSpace,
+                                        $Y1,
+                                        $X + $XOffset + $XSize - $XSpace,
+                                        $Y2,
+                                        $Color
+                                    );
+
+                                    if ($InnerColor != null) {
+                                        $this->drawRectangle(
+                                            $X + $XOffset + $XSpace + 1,
+                                            min($Y1, $Y2) + 1,
+                                            $X + $XOffset + $XSize - $XSpace - 1,
+                                            max($Y1, $Y2) - 1,
+                                            $InnerColor
+                                        );
+                                    }
+
+                                    if ($Gradient) {
+                                        $this->Shadow = false;
+
+                                        if ($GradientMode == GRADIENT_SIMPLE) {
+                                            if ($Serie["Data"][$Key] >= 0) {
+                                                $GradienColor = [
+                                                    "StartR" => $GradientStartR,
+                                                    "StartG" => $GradientStartG,
+                                                    "StartB" => $GradientStartB,
+                                                    "EndR" => $GradientEndR,
+                                                    "EndG" => $GradientEndG,
+                                                    "EndB" => $GradientEndB,
+                                                    "Alpha" => $GradientAlpha
+                                                ];
+                                            } else {
+                                                $GradienColor = [
+                                                    "StartR" => $GradientEndR,
+                                                    "StartG" => $GradientEndG,
+                                                    "StartB" => $GradientEndB,
+                                                    "EndR" => $GradientStartR,
+                                                    "EndG" => $GradientStartG,
+                                                    "EndB" => $GradientStartB,
+                                                    "Alpha" => $GradientAlpha
+                                                ];
+                                            }
+                                            $this->drawGradientArea(
+                                                $X + $XOffset + $XSpace,
+                                                $Y1,
+                                                $X + $XOffset + $XSize - $XSpace,
+                                                $Y2,
+                                                DIRECTION_VERTICAL,
+                                                $GradienColor
+                                            );
+                                        } elseif ($GradientMode == GRADIENT_EFFECT_CAN) {
+                                            $GradienColor1 = [
+                                                "StartR" => $GradientEndR,
+                                                "StartG" => $GradientEndG,
+                                                "StartB" => $GradientEndB,
+                                                "EndR" => $GradientStartR,
+                                                "EndG" => $GradientStartG,
+                                                "EndB" => $GradientStartB,
+                                                "Alpha" => $GradientAlpha
+                                            ];
+                                            $GradienColor2 = [
+                                                "StartR" => $GradientStartR,
+                                                "StartG" => $GradientStartG,
+                                                "StartB" => $GradientStartB,
+                                                "EndR" => $GradientEndR,
+                                                "EndG" => $GradientEndG,
+                                                "EndB" => $GradientEndB,
+                                                "Alpha" => $GradientAlpha
+                                            ];
+                                            $XSpan = floor($XSize / 3);
+
+                                            $this->drawGradientArea(
+                                                $X + $XOffset + $XSpace,
+                                                $Y1,
+                                                $X + $XOffset + $XSpan - $XSpace,
+                                                $Y2,
+                                                DIRECTION_HORIZONTAL,
+                                                $GradienColor1
+                                            );
+                                            $this->drawGradientArea(
+                                                $X + $XOffset + $XSpan + $XSpace,
+                                                $Y1,
+                                                $X + $XOffset + $XSize - $XSpace,
+                                                $Y2,
+                                                DIRECTION_HORIZONTAL,
+                                                $GradienColor2
+                                            );
+                                        }
+                                        $this->Shadow = $RestoreShadow;
+                                    }
+                                }
+
+                                if ($Draw0Line) {
+                                    $Line0Color = ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20];
+
+                                    if (abs($Y1 - $Y2) > 3) {
+                                        $Line0Width = 3;
+                                    } else {
+                                        $Line0Width = 1;
+                                    }
+                                    if ($Y1 - $Y2 < 0) {
+                                        $Line0Width = -$Line0Width;
+                                    }
+
+                                    $this->drawFilledRectangle(
+                                        $X + $XOffset + $XSpace,
+                                        floor($Y1),
+                                        $X + $XOffset + $XSize - $XSpace,
+                                        floor($Y1) - $Line0Width,
+                                        $Line0Color
+                                    );
+                                    $this->drawLine(
+                                        $X + $XOffset + $XSpace,
+                                        floor($Y1),
+                                        $X + $XOffset + $XSize - $XSpace,
+                                        floor($Y1),
+                                        $Line0Color
+                                    );
+                                }
+                            }
+
+                            if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                                if ($DisplayShadow) {
+                                    $this->Shadow = true;
+                                }
+
+                                $Caption = $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit);
+                                $TxtPos = $this->getTextBox(0, 0, $DisplayFont, $DisplaySize, 90, $Caption);
+                                $TxtHeight = $TxtPos[0]["Y"] - $TxtPos[1]["Y"] + $TxtMargin;
+
+                                if ($DisplayPos == LABEL_POS_INSIDE && abs($TxtHeight) < abs($BarHeight)) {
+                                    $CenterX = (($X + $XOffset + $XSize - $XSpace) - ($X + $XOffset + $XSpace))
+                                        / 2 + $X + $XOffset + $XSpace
+                                    ;
+                                    $CenterY = ($Y2 - $Y1) / 2 + $Y1;
+
+                                    $this->drawText(
+                                        $CenterX,
+                                        $CenterY,
+                                        $Caption,
+                                        [
+                                            "R" => $DisplayR,
+                                            "G" => $DisplayG,
+                                            "B" => $DisplayB,
+                                            "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                            "FontSize" => $DisplaySize,
+                                            "Angle" => 90
+                                        ]
+                                    );
+                                } else {
+                                    if ($Serie["Data"][$Key] >= 0) {
+                                        $Align = TEXT_ALIGN_BOTTOMMIDDLE;
+                                        $Offset = $DisplayOffset;
+                                    } else {
+                                        $Align = TEXT_ALIGN_TOPMIDDLE;
+                                        $Offset = -$DisplayOffset;
+                                    }
+                                    $this->drawText(
+                                        $X + $XOffset + $XSize / 2,
+                                        $Y2 - $Offset,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit),
+                                        [
+                                            "R" => $DisplayR,
+                                            "G" => $DisplayG,
+                                            "B" => $DisplayB,
+                                            "Align" => $Align,
+                                            "FontSize" => $DisplaySize
+                                        ]
+                                    );
+                                }
+
+                                $this->Shadow = $RestoreShadow;
+                            }
+                        }
+
+                        $X = $X + $XStep;
+                        $ID++;
+                    }
+                } else {
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $YStep = 0;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    if ($AroundZero) {
+                        $X1 = $YZero;
+                    } else {
+                        $X1 = $this->GraphAreaX1 + 1;
+                    }
+                    if ($XDivs == 0) {
+                        $YSize = ($this->GraphAreaY2 - $this->GraphAreaY1) / ($SeriesCount + $Interleave);
+                    } else {
+                        $YSize = ($YStep / ($SeriesCount + $Interleave));
+                    }
+
+                    $YOffset = -($YSize * $SeriesCount) / 2 + $CurrentSerie * $YSize;
+                    if ($Y + $YOffset <= $this->GraphAreaY1) {
+                        $YOffset = $this->GraphAreaY1 - $Y + 1;
+                    }
+
+                    $this->DataSet->Data["Series"][$SerieName]["XOffset"] = $YOffset + $YSize / 2;
+
+                    if ($Rounded || $BorderR != -1) {
+                        $YSpace = 1;
+                    } else {
+                        $YSpace = 0;
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $ID = 0;
+                    foreach ($PosArray as $Key => $X2) {
+                        if ($Floating0Serie != null) {
+                            if (isset($Data["Series"][$Floating0Serie]["Data"][$Key])) {
+                                $Value = $Data["Series"][$Floating0Serie]["Data"][$Key];
+                            } else {
+                                $Value = 0;
+                            }
+
+                            $YZero = $this->scaleComputeY($Value, ["AxisID" => $Serie["Axis"]]);
+                            if ($YZero < $this->GraphAreaX1 + 1) {
+                                $YZero = $this->GraphAreaX1 + 1;
+                            }
+                            if ($YZero > $this->GraphAreaX2 - 1) {
+                                $YZero = $this->GraphAreaX2 - 1;
+                            }
+                            if ($AroundZero) {
+                                $X1 = $YZero;
+                            } else {
+                                $X1 = $this->GraphAreaX1 + 1;
+                            }
+                        }
+
+                        if ($OverrideColors != null) {
+                            if (isset($OverrideColors[$ID])) {
+                                $Color = [
+                                    "R" => $OverrideColors[$ID]["R"],
+                                    "G" => $OverrideColors[$ID]["G"],
+                                    "B" => $OverrideColors[$ID]["B"],
+                                    "Alpha" => $OverrideColors[$ID]["Alpha"],
+                                    "BorderR" => $OverrideColors[$ID]["BorderR"],
+                                    "BorderG" => $OverrideColors[$ID]["BorderG"],
+                                    "BorderB" => $OverrideColors[$ID]["BorderB"]
+                                ];
+                            } else {
+                                $Color = $this->getRandomColor();
+                            }
+                        }
+
+                        if ($X2 != VOID) {
+                            $BarWidth = $X2 - $X1;
+                            if ($Serie["Data"][$Key] == 0) {
+                                $this->drawLine(
+                                    $X1,
+                                    $Y + $YOffset + $YSpace,
+                                    $X1,
+                                    $Y + $YOffset + $YSize - $YSpace,
+                                    $Color
+                                );
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            "%s,%s,%s,%s",
+                                            floor($X1 - 1),
+                                            floor($Y + $YOffset + $YSpace),
+                                            floor($X1 + 1),
+                                            floor($Y + $YOffset + $YSize - $YSpace)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+                            } else {
+                                if ($RecordImageMap) {
+                                    $this->addToImageMap(
+                                        "RECT",
+                                        sprintf(
+                                            "%s,%s,%s,%s",
+                                            floor($X1),
+                                            floor($Y + $YOffset + $YSpace),
+                                            floor($X2),
+                                            floor($Y + $YOffset + $YSize - $YSpace)
+                                        ),
+                                        $this->toHTMLColor($R, $G, $B),
+                                        $SerieDescription,
+                                        $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                    );
+                                }
+
+                                if ($Rounded) {
+                                    $this->drawRoundedFilledRectangle(
+                                        $X1 + 1,
+                                        $Y + $YOffset + $YSpace,
+                                        $X2,
+                                        $Y + $YOffset + $YSize - $YSpace,
+                                        $RoundRadius,
+                                        $Color
+                                    );
+                                } else {
+                                    $this->drawFilledRectangle(
+                                        $X1,
+                                        $Y + $YOffset + $YSpace,
+                                        $X2,
+                                        $Y + $YOffset + $YSize - $YSpace,
+                                        $Color
+                                    );
+
+                                    if ($InnerColor != null) {
+                                        $this->drawRectangle(
+                                            min($X1, $X2) + 1,
+                                            $Y + $YOffset + $YSpace + 1,
+                                            max($X1, $X2) - 1,
+                                            $Y + $YOffset + $YSize - $YSpace - 1,
+                                            $InnerColor
+                                        );
+                                    }
+
+                                    if ($Gradient) {
+                                        $this->Shadow = false;
+
+                                        if ($GradientMode == GRADIENT_SIMPLE) {
+                                            if ($Serie["Data"][$Key] >= 0) {
+                                                $GradienColor = [
+                                                    "StartR" => $GradientStartR,
+                                                    "StartG" => $GradientStartG,
+                                                    "StartB" => $GradientStartB,
+                                                    "EndR" => $GradientEndR,
+                                                    "EndG" => $GradientEndG,
+                                                    "EndB" => $GradientEndB,
+                                                    "Alpha" => $GradientAlpha
+                                                ];
+                                            } else {
+                                                $GradienColor = [
+                                                    "StartR" => $GradientEndR,
+                                                    "StartG" => $GradientEndG,
+                                                    "StartB" => $GradientEndB,
+                                                    "EndR" => $GradientStartR,
+                                                    "EndG" => $GradientStartG,
+                                                    "EndB" => $GradientStartB,
+                                                    "Alpha" => $GradientAlpha
+                                                ];
+                                            }
+                                            $this->drawGradientArea(
+                                                $X1,
+                                                $Y + $YOffset + $YSpace,
+                                                $X2,
+                                                $Y + $YOffset + $YSize - $YSpace,
+                                                DIRECTION_HORIZONTAL,
+                                                $GradienColor
+                                            );
+                                        } elseif ($GradientMode == GRADIENT_EFFECT_CAN) {
+                                            $GradienColor1 = [
+                                                "StartR" => $GradientEndR,
+                                                "StartG" => $GradientEndG,
+                                                "StartB" => $GradientEndB,
+                                                "EndR" => $GradientStartR,
+                                                "EndG" => $GradientStartG,
+                                                "EndB" => $GradientStartB,
+                                                "Alpha" => $GradientAlpha
+                                            ];
+                                            $GradienColor2 = [
+                                                "StartR" => $GradientStartR,
+                                                "StartG" => $GradientStartG,
+                                                "StartB" => $GradientStartB,
+                                                "EndR" => $GradientEndR,
+                                                "EndG" => $GradientEndG,
+                                                "EndB" => $GradientEndB,
+                                                "Alpha" => $GradientAlpha
+                                            ];
+                                            $YSpan = floor($YSize / 3);
+
+                                            $this->drawGradientArea(
+                                                $X1,
+                                                $Y + $YOffset + $YSpace,
+                                                $X2,
+                                                $Y + $YOffset + $YSpan - $YSpace,
+                                                DIRECTION_VERTICAL,
+                                                $GradienColor1
+                                            );
+                                            $this->drawGradientArea(
+                                                $X1,
+                                                $Y + $YOffset + $YSpan,
+                                                $X2,
+                                                $Y + $YOffset + $YSize - $YSpace,
+                                                DIRECTION_VERTICAL,
+                                                $GradienColor2
+                                            );
+                                        }
+                                        $this->Shadow = $RestoreShadow;
+                                    }
+                                }
+
+                                if ($Draw0Line) {
+                                    $Line0Color = ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20];
+
+                                    if (abs($X1 - $X2) > 3) {
+                                        $Line0Width = 3;
+                                    } else {
+                                        $Line0Width = 1;
+                                    }
+                                    if ($X2 - $X1 < 0) {
+                                        $Line0Width = -$Line0Width;
+                                    }
+
+                                    $this->drawFilledRectangle(
+                                        floor($X1),
+                                        $Y + $YOffset + $YSpace,
+                                        floor($X1) + $Line0Width,
+                                        $Y + $YOffset + $YSize - $YSpace,
+                                        $Line0Color
+                                    );
+                                    $this->drawLine(
+                                        floor($X1),
+                                        $Y + $YOffset + $YSpace,
+                                        floor($X1),
+                                        $Y + $YOffset + $YSize - $YSpace,
+                                        $Line0Color
+                                    );
+                                }
+                            }
+
+                            if ($DisplayValues && $Serie["Data"][$Key] != VOID) {
+                                if ($DisplayShadow) {
+                                    $this->Shadow = true;
+                                }
+
+                                $Caption = $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit);
+                                $TxtPos = $this->getTextBox(0, 0, $DisplayFont, $DisplaySize, 0, $Caption);
+                                $TxtWidth = $TxtPos[1]["X"] - $TxtPos[0]["X"] + $TxtMargin;
+
+                                if ($DisplayPos == LABEL_POS_INSIDE && abs($TxtWidth) < abs($BarWidth)) {
+                                    $CenterX = ($X2 - $X1) / 2 + $X1;
+                                    $CenterY = (($Y + $YOffset + $YSize - $YSpace)
+                                        - ($Y + $YOffset + $YSpace)) / 2
+                                        + ($Y + $YOffset + $YSpace)
+                                    ;
+
+                                    $this->drawText(
+                                        $CenterX,
+                                        $CenterY,
+                                        $Caption,
+                                        [
+                                            "R" => $DisplayR,
+                                            "G" => $DisplayG,
+                                            "B" => $DisplayB,
+                                            "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                            "FontSize" => $DisplaySize
+                                        ]
+                                    );
+                                } else {
+                                    if ($Serie["Data"][$Key] >= 0) {
+                                        $Align = TEXT_ALIGN_MIDDLELEFT;
+                                        $Offset = $DisplayOffset;
+                                    } else {
+                                        $Align = TEXT_ALIGN_MIDDLERIGHT;
+                                        $Offset = -$DisplayOffset;
+                                    }
+                                    $this->drawText(
+                                        $X2 + $Offset,
+                                        $Y + $YOffset + $YSize / 2,
+                                        $Caption,
+                                        [
+                                            "R" => $DisplayR,
+                                            "G" => $DisplayG,
+                                            "B" => $DisplayB,
+                                            "Align" => $Align,
+                                            "FontSize" => $DisplaySize
+                                        ]
+                                    );
+                                }
+
+                                $this->Shadow = $RestoreShadow;
+                            }
+                        }
+                        $Y = $Y + $YStep;
+                        $ID++;
+                    }
+                }
+                $CurrentSerie++;
+            }
+        }
+    }
+
+    /**
+     * Draw a bar chart
+     * @param array $Format
+     */
+    public function drawStackedBarChart(array $Format = [])
+    {
+        $DisplayValues = isset($Format["DisplayValues"]) ? $Format["DisplayValues"] : false;
+        $DisplayOrientation = isset($Format["DisplayOrientation"]) ? $Format["DisplayOrientation"] : ORIENTATION_AUTO;
+        $DisplayRound = isset($Format["DisplayRound"]) ? $Format["DisplayRound"] : 0;
+        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
+        $DisplayFont = isset($Format["DisplayFont"]) ? $Format["DisplayFont"] : $this->FontName;
+        $DisplaySize = isset($Format["DisplaySize"]) ? $Format["DisplaySize"] : $this->FontSize;
+        $DisplayR = isset($Format["DisplayR"]) ? $Format["DisplayR"] : 0;
+        $DisplayG = isset($Format["DisplayG"]) ? $Format["DisplayG"] : 0;
+        $DisplayB = isset($Format["DisplayB"]) ? $Format["DisplayB"] : 0;
+        $Interleave = isset($Format["Interleave"]) ? $Format["Interleave"] : .5;
+        $Rounded = isset($Format["Rounded"]) ? $Format["Rounded"] : false;
+        $RoundRadius = isset($Format["RoundRadius"]) ? $Format["RoundRadius"] : 4;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : -1;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : -1;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : -1;
+        $Gradient = isset($Format["Gradient"]) ? $Format["Gradient"] : false;
+        $GradientMode = isset($Format["GradientMode"]) ? $Format["GradientMode"] : GRADIENT_SIMPLE;
+        $GradientAlpha = isset($Format["GradientAlpha"]) ? $Format["GradientAlpha"] : 20;
+        $GradientStartR = isset($Format["GradientStartR"]) ? $Format["GradientStartR"] : 255;
+        $GradientStartG = isset($Format["GradientStartG"]) ? $Format["GradientStartG"] : 255;
+        $GradientStartB = isset($Format["GradientStartB"]) ? $Format["GradientStartB"] : 255;
+        $GradientEndR = isset($Format["GradientEndR"]) ? $Format["GradientEndR"] : 0;
+        $GradientEndG = isset($Format["GradientEndG"]) ? $Format["GradientEndG"] : 0;
+        $GradientEndB = isset($Format["GradientEndB"]) ? $Format["GradientEndB"] : 0;
+        $InnerSurrounding = isset($Format["InnerSurrounding"]) ? $Format["InnerSurrounding"] : null;
+        $InnerBorderR = isset($Format["InnerBorderR"]) ? $Format["InnerBorderR"] : -1;
+        $InnerBorderG = isset($Format["InnerBorderG"]) ? $Format["InnerBorderG"] : -1;
+        $InnerBorderB = isset($Format["InnerBorderB"]) ? $Format["InnerBorderB"] : -1;
+        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
+        $FontFactor = isset($Format["FontFactor"]) ? $Format["FontFactor"] : 8;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_STACKED;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        $RestoreShadow = $this->Shadow;
+
+        $LastX = [];
+        $LastY = [];
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($DisplayColor == DISPLAY_AUTO) {
+                    $DisplayR = 255;
+                    $DisplayG = 255;
+                    $DisplayB = 255;
+                }
+                if ($Surrounding != null) {
+                    $BorderR = $R + $Surrounding;
+                    $BorderG = $G + $Surrounding;
+                    $BorderB = $B + $Surrounding;
+                }
+                if ($InnerSurrounding != null) {
+                    $InnerBorderR = $R + $InnerSurrounding;
+                    $InnerBorderG = $G + $InnerSurrounding;
+                    $InnerBorderB = $B + $InnerSurrounding;
+                }
+                if ($InnerBorderR == -1) {
+                    $InnerColor = null;
+                } else {
+                    $InnerColor = [
+                        "R" => $InnerBorderR,
+                        "G" => $InnerBorderG,
+                        "B" => $InnerBorderB
+                    ];
+                }
+
+                $AxisID = $Serie["Axis"];
+                $Mode = $Data["Axis"][$AxisID]["Display"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+                $Unit = $Data["Axis"][$AxisID]["Unit"];
+
+                if (isset($Serie["Description"])) {
+                    $SerieDescription = $Serie["Description"];
+                } else {
+                    $SerieDescription = $SerieName;
+                }
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]],
+                    true
+                );
+                $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                $Color = [
+                    "TransCorner" => true,
+                    "R" => $R,
+                    "G" => $G,
+                    "B" => $B,
+                    "Alpha" => $Alpha,
+                    "BorderR" => $BorderR,
+                    "BorderG" => $BorderG,
+                    "BorderB" => $BorderB
+                ];
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    $XSize = ($XStep / (1 + $Interleave));
+                    $XOffset = -($XSize / 2);
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $Height) {
+                        if ($Height != VOID && $Serie["Data"][$Key] != 0) {
+                            if ($Serie["Data"][$Key] > 0) {
+                                $Pos = "+";
+                            } else {
+                                $Pos = "-";
+                            }
+
+                            if (!isset($LastY[$Key])) {
+                                $LastY[$Key] = [];
+                            }
+                            if (!isset($LastY[$Key][$Pos])) {
+                                $LastY[$Key][$Pos] = $YZero;
+                            }
+
+                            $Y1 = $LastY[$Key][$Pos];
+                            $Y2 = $Y1 - $Height;
+
+                            if (($Rounded || $BorderR != -1) && ($Pos == "+" && $Y1 != $YZero)) {
+                                $YSpaceUp = 1;
+                            } else {
+                                $YSpaceUp = 0;
+                            }
+                            if (($Rounded || $BorderR != -1) && ($Pos == "-" && $Y1 != $YZero)) {
+                                $YSpaceDown = 1;
+                            } else {
+                                $YSpaceDown = 0;
+                            }
+
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        "%s,%s,%s,%s",
+                                        floor($X + $XOffset),
+                                        floor($Y1 - $YSpaceUp + $YSpaceDown),
+                                        floor($X + $XOffset + $XSize),
+                                        floor($Y2)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+
+                            if ($Rounded) {
+                                $this->drawRoundedFilledRectangle(
+                                    $X + $XOffset,
+                                    $Y1 - $YSpaceUp + $YSpaceDown,
+                                    $X + $XOffset + $XSize,
+                                    $Y2,
+                                    $RoundRadius,
+                                    $Color
+                                );
+                            } else {
+                                $this->drawFilledRectangle(
+                                    $X + $XOffset,
+                                    $Y1 - $YSpaceUp + $YSpaceDown,
+                                    $X + $XOffset + $XSize,
+                                    $Y2,
+                                    $Color
+                                );
+
+                                if ($InnerColor != null) {
+                                    $RestoreShadow = $this->Shadow;
+                                    $this->Shadow = false;
+                                    $this->drawRectangle(
+                                        min($X + $XOffset + 1, $X + $XOffset + $XSize),
+                                        min($Y1 - $YSpaceUp + $YSpaceDown, $Y2) + 1,
+                                        max($X + $XOffset + 1, $X + $XOffset + $XSize) - 1,
+                                        max($Y1 - $YSpaceUp + $YSpaceDown, $Y2) - 1,
+                                        $InnerColor
+                                    );
+                                    $this->Shadow = $RestoreShadow;
+                                }
+
+                                if ($Gradient) {
+                                    $this->Shadow = false;
+
+                                    if ($GradientMode == GRADIENT_SIMPLE) {
+                                        $GradientColor = [
+                                            "StartR" => $GradientStartR,
+                                            "StartG" => $GradientStartG,
+                                            "StartB" => $GradientStartB,
+                                            "EndR" => $GradientEndR,
+                                            "EndG" => $GradientEndG,
+                                            "EndB" => $GradientEndB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $this->drawGradientArea(
+                                            $X + $XOffset,
+                                            $Y1 - 1 - $YSpaceUp + $YSpaceDown,
+                                            $X + $XOffset + $XSize,
+                                            $Y2 + 1,
+                                            DIRECTION_VERTICAL,
+                                            $GradientColor
+                                        );
+                                    } elseif ($GradientMode == GRADIENT_EFFECT_CAN) {
+                                        $GradientColor1 = [
+                                            "StartR" => $GradientEndR,
+                                            "StartG" => $GradientEndG,
+                                            "StartB" => $GradientEndB,
+                                            "EndR" => $GradientStartR,
+                                            "EndG" => $GradientStartG,
+                                            "EndB" => $GradientStartB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $GradientColor2 = [
+                                            "StartR" => $GradientStartR,
+                                            "StartG" => $GradientStartG,
+                                            "StartB" => $GradientStartB,
+                                            "EndR" => $GradientEndR,
+                                            "EndG" => $GradientEndG,
+                                            "EndB" => $GradientEndB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $XSpan = floor($XSize / 3);
+
+                                        $this->drawGradientArea(
+                                            $X + $XOffset - .5,
+                                            $Y1 - .5 - $YSpaceUp + $YSpaceDown,
+                                            $X + $XOffset + $XSpan,
+                                            $Y2 + .5,
+                                            DIRECTION_HORIZONTAL,
+                                            $GradientColor1
+                                        );
+                                        $this->drawGradientArea(
+                                            $X + $XSpan + $XOffset - .5,
+                                            $Y1 - .5 - $YSpaceUp + $YSpaceDown,
+                                            $X + $XOffset + $XSize,
+                                            $Y2 + .5,
+                                            DIRECTION_HORIZONTAL,
+                                            $GradientColor2
+                                        );
+                                    }
+                                    $this->Shadow = $RestoreShadow;
+                                }
+                            }
+
+                            if ($DisplayValues) {
+                                $BarHeight = abs($Y2 - $Y1) - 2;
+                                $BarWidth = $XSize + ($XOffset / 2) - $FontFactor;
+
+                                $Caption = $this->scaleFormat(
+                                    round($Serie["Data"][$Key], $DisplayRound),
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                );
+                                $TxtPos = $this->getTextBox(0, 0, $DisplayFont, $DisplaySize, 0, $Caption);
+                                $TxtHeight = abs($TxtPos[2]["Y"] - $TxtPos[0]["Y"]);
+                                $TxtWidth = abs($TxtPos[1]["X"] - $TxtPos[0]["X"]);
+
+                                $XCenter = (($X + $XOffset + $XSize) - ($X + $XOffset)) / 2 + $X + $XOffset;
+                                $YCenter = (($Y2) - ($Y1 - $YSpaceUp + $YSpaceDown)) / 2
+                                    + $Y1 - $YSpaceUp + $YSpaceDown
+                                ;
+
+                                $Done = false;
+                                if ($DisplayOrientation == ORIENTATION_HORIZONTAL
+                                    || $DisplayOrientation == ORIENTATION_AUTO
+                                ) {
+                                    if ($TxtHeight < $BarHeight && $TxtWidth < $BarWidth) {
+                                        $this->drawText(
+                                            $XCenter,
+                                            $YCenter,
+                                            $this->scaleFormat(
+                                                $Serie["Data"][$Key],
+                                                $Mode,
+                                                $Format,
+                                                $Unit
+                                            ),
+                                            [
+                                                "R" => $DisplayR,
+                                                "G" => $DisplayG,
+                                                "B" => $DisplayB,
+                                                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                                "FontSize" => $DisplaySize,
+                                                "FontName" => $DisplayFont
+                                            ]
+                                        );
+                                        $Done = true;
+                                    }
+                                }
+
+                                if ($DisplayOrientation == ORIENTATION_VERTICAL
+                                    || ($DisplayOrientation == ORIENTATION_AUTO && !$Done)
+                                ) {
+                                    if ($TxtHeight < $BarWidth && $TxtWidth < $BarHeight) {
+                                        $this->drawText(
+                                            $XCenter,
+                                            $YCenter,
+                                            $this->scaleFormat(
+                                                $Serie["Data"][$Key],
+                                                $Mode,
+                                                $Format,
+                                                $Unit
+                                            ),
+                                            [
+                                                "R" => $DisplayR,
+                                                "G" => $DisplayG,
+                                                "B" => $DisplayB,
+                                                "Angle" => 90,
+                                                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                                "FontSize" => $DisplaySize,
+                                                "FontName" => $DisplayFont
+                                            ]
+                                        );
+                                    }
+                                }
+                            }
+                            $LastY[$Key][$Pos] = $Y2;
+                        }
+                        $X = $X + $XStep;
+                    }
+                } else {
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    $YSize = $YStep / (1 + $Interleave);
+                    $YOffset = -($YSize / 2);
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    foreach ($PosArray as $Key => $Width) {
+                        if ($Width != VOID && $Serie["Data"][$Key] != 0) {
+                            if ($Serie["Data"][$Key] > 0) {
+                                $Pos = "+";
+                            } else {
+                                $Pos = "-";
+                            }
+
+                            if (!isset($LastX[$Key])) {
+                                $LastX[$Key] = [];
+                            }
+                            if (!isset($LastX[$Key][$Pos])) {
+                                $LastX[$Key][$Pos] = $YZero;
+                            }
+
+                            $X1 = $LastX[$Key][$Pos];
+                            $X2 = $X1 + $Width;
+
+                            if (($Rounded || $BorderR != -1) && ($Pos == "+" && $X1 != $YZero)) {
+                                $XSpaceLeft = 2;
+                            } else {
+                                $XSpaceLeft = 0;
+                            }
+                            if (($Rounded || $BorderR != -1) && ($Pos == "-" && $X1 != $YZero)) {
+                                $XSpaceRight = 2;
+                            } else {
+                                $XSpaceRight = 0;
+                            }
+
+                            if ($RecordImageMap) {
+                                $this->addToImageMap(
+                                    "RECT",
+                                    sprintf(
+                                        "%s,%s,%s,%s",
+                                        floor($X1 + $XSpaceLeft),
+                                        floor($Y + $YOffset),
+                                        floor($X2 - $XSpaceRight),
+                                        floor($Y + $YOffset + $YSize)
+                                    ),
+                                    $this->toHTMLColor($R, $G, $B),
+                                    $SerieDescription,
+                                    $this->scaleFormat($Serie["Data"][$Key], $Mode, $Format, $Unit)
+                                );
+                            }
+
+                            if ($Rounded) {
+                                $this->drawRoundedFilledRectangle(
+                                    $X1 + $XSpaceLeft,
+                                    $Y + $YOffset,
+                                    $X2 - $XSpaceRight,
+                                    $Y + $YOffset + $YSize,
+                                    $RoundRadius,
+                                    $Color
+                                );
+                            } else {
+                                $this->drawFilledRectangle(
+                                    $X1 + $XSpaceLeft,
+                                    $Y + $YOffset,
+                                    $X2 - $XSpaceRight,
+                                    $Y + $YOffset + $YSize,
+                                    $Color
+                                );
+
+                                if ($InnerColor != null) {
+                                    $RestoreShadow = $this->Shadow;
+                                    $this->Shadow = false;
+                                    $this->drawRectangle(
+                                        min($X1 + $XSpaceLeft, $X2 - $XSpaceRight) + 1,
+                                        min($Y + $YOffset, $Y + $YOffset + $YSize) + 1,
+                                        max($X1 + $XSpaceLeft, $X2 - $XSpaceRight) - 1,
+                                        max($Y + $YOffset, $Y + $YOffset + $YSize) - 1,
+                                        $InnerColor
+                                    );
+                                    $this->Shadow = $RestoreShadow;
+                                }
+
+                                if ($Gradient) {
+                                    $this->Shadow = false;
+
+                                    if ($GradientMode == GRADIENT_SIMPLE) {
+                                        $GradientColor = [
+                                            "StartR" => $GradientStartR,
+                                            "StartG" => $GradientStartG,
+                                            "StartB" => $GradientStartB,
+                                            "EndR" => $GradientEndR,
+                                            "EndG" => $GradientEndG,
+                                            "EndB" => $GradientEndB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $this->drawGradientArea(
+                                            $X1 + $XSpaceLeft,
+                                            $Y + $YOffset,
+                                            $X2 - $XSpaceRight,
+                                            $Y + $YOffset + $YSize,
+                                            DIRECTION_HORIZONTAL,
+                                            $GradientColor
+                                        );
+                                    } elseif ($GradientMode == GRADIENT_EFFECT_CAN) {
+                                        $GradientColor1 = [
+                                            "StartR" => $GradientEndR,
+                                            "StartG" => $GradientEndG,
+                                            "StartB" => $GradientEndB,
+                                            "EndR" => $GradientStartR,
+                                            "EndG" => $GradientStartG,
+                                            "EndB" => $GradientStartB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $GradientColor2 = [
+                                            "StartR" => $GradientStartR,
+                                            "StartG" => $GradientStartG,
+                                            "StartB" => $GradientStartB,
+                                            "EndR" => $GradientEndR,
+                                            "EndG" => $GradientEndG,
+                                            "EndB" => $GradientEndB,
+                                            "Alpha" => $GradientAlpha
+                                        ];
+                                        $YSpan = floor($YSize / 3);
+
+                                        $this->drawGradientArea(
+                                            $X1 + $XSpaceLeft,
+                                            $Y + $YOffset,
+                                            $X2 - $XSpaceRight,
+                                            $Y + $YOffset + $YSpan,
+                                            DIRECTION_VERTICAL,
+                                            $GradientColor1
+                                        );
+                                        $this->drawGradientArea(
+                                            $X1 + $XSpaceLeft,
+                                            $Y + $YOffset + $YSpan,
+                                            $X2 - $XSpaceRight,
+                                            $Y + $YOffset + $YSize,
+                                            DIRECTION_VERTICAL,
+                                            $GradientColor2
+                                        );
+                                    }
+                                    $this->Shadow = $RestoreShadow;
+                                }
+                            }
+
+                            if ($DisplayValues) {
+                                $BarWidth = abs($X2 - $X1) - $FontFactor;
+                                $BarHeight = $YSize + ($YOffset / 2) - $FontFactor / 2;
+                                $Caption = $this->scaleFormat(
+                                    round($Serie["Data"][$Key], $DisplayRound),
+                                    $Mode,
+                                    $Format,
+                                    $Unit
+                                );
+                                $TxtPos = $this->getTextBox(0, 0, $DisplayFont, $DisplaySize, 0, $Caption);
+                                $TxtHeight = abs($TxtPos[2]["Y"] - $TxtPos[0]["Y"]);
+                                $TxtWidth = abs($TxtPos[1]["X"] - $TxtPos[0]["X"]);
+
+                                $XCenter = ($X2 - $X1) / 2 + $X1;
+                                $YCenter = (($Y + $YOffset + $YSize) - ($Y + $YOffset)) / 2 + $Y + $YOffset;
+
+                                $Done = false;
+                                if ($DisplayOrientation == ORIENTATION_HORIZONTAL
+                                    || $DisplayOrientation == ORIENTATION_AUTO
+                                ) {
+                                    if ($TxtHeight < $BarHeight && $TxtWidth < $BarWidth) {
+                                        $this->drawText(
+                                            $XCenter,
+                                            $YCenter,
+                                            $this->scaleFormat(
+                                                $Serie["Data"][$Key],
+                                                $Mode,
+                                                $Format,
+                                                $Unit
+                                            ),
+                                            [
+                                                "R" => $DisplayR,
+                                                "G" => $DisplayG,
+                                                "B" => $DisplayB,
+                                                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                                "FontSize" => $DisplaySize,
+                                                "FontName" => $DisplayFont
+                                            ]
+                                        );
+                                        $Done = true;
+                                    }
+                                }
+
+                                if ($DisplayOrientation == ORIENTATION_VERTICAL
+                                    || ($DisplayOrientation == ORIENTATION_AUTO && !$Done)
+                                ) {
+                                    if ($TxtHeight < $BarWidth && $TxtWidth < $BarHeight) {
+                                        $this->drawText(
+                                            $XCenter,
+                                            $YCenter,
+                                            $this->scaleFormat(
+                                                $Serie["Data"][$Key],
+                                                $Mode,
+                                                $Format,
+                                                $Unit
+                                            ),
+                                            [
+                                                "R" => $DisplayR,
+                                                "G" => $DisplayG,
+                                                "B" => $DisplayB,
+                                                "Angle" => 90,
+                                                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
+                                                "FontSize" => $DisplaySize,
+                                                "FontName" => $DisplayFont
+                                            ]
+                                        );
+                                    }
+                                }
+                            }
+
+                            $LastX[$Key][$Pos] = $X2;
+                        }
+
+                        $Y = $Y + $YStep;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a stacked area chart
+     * @param array $Format
+     */
+    public function drawStackedAreaChart(array $Format = [])
+    {
+        $DrawLine = isset($Format["DrawLine"]) ? $Format["DrawLine"] : false;
+        $LineSurrounding = isset($Format["LineSurrounding"]) ? $Format["LineSurrounding"] : null;
+        $LineR = isset($Format["LineR"]) ? $Format["LineR"] : VOID;
+        $LineG = isset($Format["LineG"]) ? $Format["LineG"] : VOID;
+        $LineB = isset($Format["LineB"]) ? $Format["LineB"] : VOID;
+        $LineAlpha = isset($Format["LineAlpha"]) ? $Format["LineAlpha"] : 100;
+        $DrawPlot = isset($Format["DrawPlot"]) ? $Format["DrawPlot"] : false;
+        $PlotRadius = isset($Format["PlotRadius"]) ? $Format["PlotRadius"] : 2;
+        $PlotBorder = isset($Format["PlotBorder"]) ? $Format["PlotBorder"] : 1;
+        $PlotBorderSurrounding = isset($Format["PlotBorderSurrounding"]) ? $Format["PlotBorderSurrounding"] : null;
+        $PlotBorderR = isset($Format["PlotBorderR"]) ? $Format["PlotBorderR"] : 0;
+        $PlotBorderG = isset($Format["PlotBorderG"]) ? $Format["PlotBorderG"] : 0;
+        $PlotBorderB = isset($Format["PlotBorderB"]) ? $Format["PlotBorderB"] : 0;
+        $PlotBorderAlpha = isset($Format["PlotBorderAlpha"]) ? $Format["PlotBorderAlpha"] : 50;
+        $ForceTransparency = isset($Format["ForceTransparency"]) ? $Format["ForceTransparency"] : null;
+
+        $this->LastChartLayout = CHART_LAST_LAYOUT_STACKED;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        $RestoreShadow = $this->Shadow;
+        $this->Shadow = false;
+
+        /* Build the offset data series */
+        $OverallOffset = [];
+        $SerieOrder = [];
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $SerieOrder[] = $SerieName;
+
+                foreach ($Serie["Data"] as $Key => $Value) {
+                    if ($Value == VOID) {
+                        $Value = 0;
+                    }
+                    if ($Value >= 0) {
+                        $Sign = "+";
+                    } else {
+                        $Sign = "-";
+                    }
+                    if (!isset($OverallOffset[$Key]) || !isset($OverallOffset[$Key][$Sign])) {
+                        $OverallOffset[$Key][$Sign] = 0;
+                    }
+
+                    if ($Sign == "+") {
+                        $Data["Series"][$SerieName]["Data"][$Key] = $Value + $OverallOffset[$Key][$Sign];
+                    } else {
+                        $Data["Series"][$SerieName]["Data"][$Key] = $Value - $OverallOffset[$Key][$Sign];
+                    }
+
+                    $OverallOffset[$Key][$Sign] = $OverallOffset[$Key][$Sign] + abs($Value);
+                }
+            }
+        }
+        $SerieOrder = array_reverse($SerieOrder);
+
+        foreach ($SerieOrder as $Key => $SerieName) {
+            $Serie = $Data["Series"][$SerieName];
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                if ($ForceTransparency != null) {
+                    $Alpha = $ForceTransparency;
+                }
+
+                $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha];
+                if ($LineSurrounding != null) {
+                    $LineColor = [
+                        "R" => $R + $LineSurrounding,
+                        "G" => $G + $LineSurrounding,
+                        "B" => $B + $LineSurrounding,
+                        "Alpha" => $Alpha
+                    ];
+                } elseif ($LineR != VOID) {
+                    $LineColor = [
+                        "R" => $LineR,
+                        "G" => $LineG,
+                        "B" => $LineB,
+                        "Alpha" => $LineAlpha
+                    ];
+                } else {
+                    $LineColor = $Color;
+                }
+                if ($PlotBorderSurrounding != null) {
+                    $PlotBorderColor = [
+                        "R" => $R + $PlotBorderSurrounding,
+                        "G" => $G + $PlotBorderSurrounding,
+                        "B" => $B + $PlotBorderSurrounding,
+                        "Alpha" => $PlotBorderAlpha
+                    ];
+                } else {
+                    $PlotBorderColor = [
+                        "R" => $PlotBorderR,
+                        "G" => $PlotBorderG,
+                        "B" => $PlotBorderB,
+                        "Alpha" => $PlotBorderAlpha
+                    ];
+                }
+                $AxisID = $Serie["Axis"];
+                $Format = $Data["Axis"][$AxisID]["Format"];
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]],
+                    true
+                );
+                $YZero = $this->scaleComputeY(0, ["AxisID" => $Serie["Axis"]]);
+
+                $this->DataSet->Data["Series"][$SerieName]["XOffset"] = 0;
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($YZero < $this->GraphAreaY1 + 1) {
+                        $YZero = $this->GraphAreaY1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaY2 - 1) {
+                        $YZero = $this->GraphAreaY2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $Plots = [];
+                    $Plots[] = $X;
+                    $Plots[] = $YZero;
+                    foreach ($PosArray as $Key => $Height) {
+                        if ($Height != VOID) {
+                            $Plots[] = $X;
+                            $Plots[] = $YZero - $Height;
+                        }
+                        $X = $X + $XStep;
+                    }
+                    $Plots[] = $X - $XStep;
+                    $Plots[] = $YZero;
+
+                    $this->drawPolygon($Plots, $Color);
+
+                    $this->Shadow = $RestoreShadow;
+                    if ($DrawLine) {
+                        for ($i = 2; $i <= count($Plots) - 6; $i = $i + 2) {
+                            $this->drawLine(
+                                $Plots[$i],
+                                $Plots[$i + 1],
+                                $Plots[$i + 2],
+                                $Plots[$i + 3],
+                                $LineColor
+                            );
+                        }
+                    }
+                    if ($DrawPlot) {
+                        for ($i = 2; $i <= count($Plots) - 4; $i = $i + 2) {
+                            if ($PlotBorder != 0) {
+                                $this->drawFilledCircle(
+                                    $Plots[$i],
+                                    $Plots[$i + 1],
+                                    $PlotRadius + $PlotBorder,
+                                    $PlotBorderColor
+                                );
+                            }
+                            $this->drawFilledCircle($Plots[$i], $Plots[$i + 1], $PlotRadius, $Color);
+                        }
+                    }
+                    $this->Shadow = false;
+                } elseif ($Data["Orientation"] == SCALE_POS_TOPBOTTOM) {
+                    if ($YZero < $this->GraphAreaX1 + 1) {
+                        $YZero = $this->GraphAreaX1 + 1;
+                    }
+                    if ($YZero > $this->GraphAreaX2 - 1) {
+                        $YZero = $this->GraphAreaX2 - 1;
+                    }
+
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $Plots = [];
+                    $Plots[] = $YZero;
+                    $Plots[] = $Y;
+                    foreach ($PosArray as $Key => $Height) {
+                        if ($Height != VOID) {
+                            $Plots[] = $YZero + $Height;
+                            $Plots[] = $Y;
+                        }
+                        $Y = $Y + $YStep;
+                    }
+                    $Plots[] = $YZero;
+                    $Plots[] = $Y - $YStep;
+
+                    $this->drawPolygon($Plots, $Color);
+
+                    $this->Shadow = $RestoreShadow;
+                    if ($DrawLine) {
+                        for ($i = 2; $i <= count($Plots) - 6; $i = $i + 2) {
+                            $this->drawLine(
+                                $Plots[$i],
+                                $Plots[$i + 1],
+                                $Plots[$i + 2],
+                                $Plots[$i + 3],
+                                $LineColor
+                            );
+                        }
+                    }
+                    if ($DrawPlot) {
+                        for ($i = 2; $i <= count($Plots) - 4; $i = $i + 2) {
+                            if ($PlotBorder != 0) {
+                                $this->drawFilledCircle(
+                                    $Plots[$i],
+                                    $Plots[$i + 1],
+                                    $PlotRadius + $PlotBorder,
+                                    $PlotBorderColor
+                                );
+                            }
+
+                            $this->drawFilledCircle($Plots[$i], $Plots[$i + 1], $PlotRadius, $Color);
+                        }
+                    }
+                    $this->Shadow = false;
+                }
+            }
+        }
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw the derivative chart associated to the data series
+     * @param array $Format
+     */
+    public function drawDerivative(array $Format = [])
+    {
+        $Offset = isset($Format["Offset"]) ? $Format["Offset"] : 10;
+        $SerieSpacing = isset($Format["SerieSpacing"]) ? $Format["SerieSpacing"] : 3;
+        $DerivativeHeight = isset($Format["DerivativeHeight"]) ? $Format["DerivativeHeight"] : 4;
+        $ShadedSlopeBox = isset($Format["ShadedSlopeBox"]) ? $Format["ShadedSlopeBox"] : false;
+        $DrawBackground = isset($Format["DrawBackground"]) ? $Format["DrawBackground"] : true;
+        $BackgroundR = isset($Format["BackgroundR"]) ? $Format["BackgroundR"] : 255;
+        $BackgroundG = isset($Format["BackgroundG"]) ? $Format["BackgroundG"] : 255;
+        $BackgroundB = isset($Format["BackgroundB"]) ? $Format["BackgroundB"] : 255;
+        $BackgroundAlpha = isset($Format["BackgroundAlpha"]) ? $Format["BackgroundAlpha"] : 20;
+        $DrawBorder = isset($Format["DrawBorder"]) ? $Format["DrawBorder"] : true;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 0;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 0;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 0;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 100;
+        $Caption = isset($Format["Caption"]) ? $Format["Caption"] : true;
+        $CaptionHeight = isset($Format["CaptionHeight"]) ? $Format["CaptionHeight"] : 10;
+        $CaptionWidth = isset($Format["CaptionWidth"]) ? $Format["CaptionWidth"] : 20;
+        $CaptionMargin = isset($Format["CaptionMargin"]) ? $Format["CaptionMargin"] : 4;
+        $CaptionLine = isset($Format["CaptionLine"]) ? $Format["CaptionLine"] : false;
+        $CaptionBox = isset($Format["CaptionBox"]) ? $Format["CaptionBox"] : false;
+        $CaptionBorderR = isset($Format["CaptionBorderR"]) ? $Format["CaptionBorderR"] : 0;
+        $CaptionBorderG = isset($Format["CaptionBorderG"]) ? $Format["CaptionBorderG"] : 0;
+        $CaptionBorderB = isset($Format["CaptionBorderB"]) ? $Format["CaptionBorderB"] : 0;
+        $CaptionFillR = isset($Format["CaptionFillR"]) ? $Format["CaptionFillR"] : 255;
+        $CaptionFillG = isset($Format["CaptionFillG"]) ? $Format["CaptionFillG"] : 255;
+        $CaptionFillB = isset($Format["CaptionFillB"]) ? $Format["CaptionFillB"] : 255;
+        $CaptionFillAlpha = isset($Format["CaptionFillAlpha"]) ? $Format["CaptionFillAlpha"] : 80;
+        $PositiveSlopeStartR = isset($Format["PositiveSlopeStartR"]) ? $Format["PositiveSlopeStartR"] : 184;
+        $PositiveSlopeStartG = isset($Format["PositiveSlopeStartG"]) ? $Format["PositiveSlopeStartG"] : 234;
+        $PositiveSlopeStartB = isset($Format["PositiveSlopeStartB"]) ? $Format["PositiveSlopeStartB"] : 88;
+        $PositiveSlopeEndR = isset($Format["PositiveSlopeStartR"]) ? $Format["PositiveSlopeStartR"] : 239;
+        $PositiveSlopeEndG = isset($Format["PositiveSlopeStartG"]) ? $Format["PositiveSlopeStartG"] : 31;
+        $PositiveSlopeEndB = isset($Format["PositiveSlopeStartB"]) ? $Format["PositiveSlopeStartB"] : 36;
+        $NegativeSlopeStartR = isset($Format["NegativeSlopeStartR"]) ? $Format["NegativeSlopeStartR"] : 184;
+        $NegativeSlopeStartG = isset($Format["NegativeSlopeStartG"]) ? $Format["NegativeSlopeStartG"] : 234;
+        $NegativeSlopeStartB = isset($Format["NegativeSlopeStartB"]) ? $Format["NegativeSlopeStartB"] : 88;
+        $NegativeSlopeEndR = isset($Format["NegativeSlopeStartR"]) ? $Format["NegativeSlopeStartR"] : 67;
+        $NegativeSlopeEndG = isset($Format["NegativeSlopeStartG"]) ? $Format["NegativeSlopeStartG"] : 124;
+        $NegativeSlopeEndB = isset($Format["NegativeSlopeStartB"]) ? $Format["NegativeSlopeStartB"] : 227;
+
+        $Data = $this->DataSet->getData();
+
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+            $YPos = $this->DataSet->Data["GraphArea"]["Y2"] + $Offset;
+        } else {
+            $XPos = $this->DataSet->Data["GraphArea"]["X2"] + $Offset;
+        }
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                $R = $Serie["Color"]["R"];
+                $G = $Serie["Color"]["G"];
+                $B = $Serie["Color"]["B"];
+                $Alpha = $Serie["Color"]["Alpha"];
+                $Ticks = $Serie["Ticks"];
+                $Weight = $Serie["Weight"];
+
+                $AxisID = $Serie["Axis"];
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($Caption) {
+                        if ($CaptionLine) {
+                            $StartX = floor($this->GraphAreaX1 - $CaptionWidth + $XMargin - $CaptionMargin);
+                            $EndX = floor($this->GraphAreaX1 - $CaptionMargin + $XMargin);
+
+                            $CaptionSettings = [
+                                "R" => $R,
+                                "G" => $G,
+                                "B" => $B,
+                                "Alpha" => $Alpha,
+                                "Ticks" => $Ticks,
+                                "Weight" => $Weight
+                            ];
+                            if ($CaptionBox) {
+                                $this->drawFilledRectangle(
+                                    $StartX,
+                                    $YPos,
+                                    $EndX,
+                                    $YPos + $CaptionHeight,
+                                    [
+                                        "R" => $CaptionFillR,
+                                        "G" => $CaptionFillG,
+                                        "B" => $CaptionFillB,
+                                        "BorderR" => $CaptionBorderR,
+                                        "BorderG" => $CaptionBorderG,
+                                        "BorderB" => $CaptionBorderB,
+                                        "Alpha" => $CaptionFillAlpha
+                                    ]
+                                );
+                            }
+                            $this->drawLine(
+                                $StartX + 2,
+                                $YPos + ($CaptionHeight / 2),
+                                $EndX - 2,
+                                $YPos + ($CaptionHeight / 2),
+                                $CaptionSettings
+                            );
+                        } else {
+                            $this->drawFilledRectangle(
+                                $this->GraphAreaX1 - $CaptionWidth + $XMargin - $CaptionMargin,
+                                $YPos,
+                                $this->GraphAreaX1 - $CaptionMargin + $XMargin,
+                                $YPos + $CaptionHeight,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "BorderR" => $CaptionBorderR,
+                                    "BorderG" => $CaptionBorderG,
+                                    "BorderB" => $CaptionBorderB
+                                ]
+                            );
+                        }
+                    }
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    $TopY = $YPos + ($CaptionHeight / 2) - ($DerivativeHeight / 2);
+                    $BottomY = $YPos + ($CaptionHeight / 2) + ($DerivativeHeight / 2);
+
+                    $StartX = floor($this->GraphAreaX1 + $XMargin);
+                    $EndX = floor($this->GraphAreaX2 - $XMargin);
+
+                    if ($DrawBackground) {
+                        $this->drawFilledRectangle(
+                            $StartX - 1,
+                            $TopY - 1,
+                            $EndX + 1,
+                            $BottomY + 1,
+                            [
+                                "R" => $BackgroundR,
+                                "G" => $BackgroundG,
+                                "B" => $BackgroundB,
+                                "Alpha" => $BackgroundAlpha
+                            ]
+                        );
+                    }
+                    if ($DrawBorder) {
+                        $this->drawRectangle(
+                            $StartX - 1,
+                            $TopY - 1,
+                            $EndX + 1,
+                            $BottomY + 1,
+                            [
+                                "R" => $BorderR,
+                                "G" => $BorderG,
+                                "B" => $BorderB,
+                                "Alpha" => $BorderAlpha
+                            ]
+                        );
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $RestoreShadow = $this->Shadow;
+                    $this->Shadow = false;
+
+                    /* Determine the Max slope index */
+                    $LastX = null;
+                    $LastY = null;
+                    $MinSlope = 0;
+                    $MaxSlope = 1;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($Y != VOID && $LastX != null) {
+                            $Slope = ($LastY - $Y);
+                            if ($Slope > $MaxSlope) {
+                                $MaxSlope = $Slope;
+                            } if ($Slope < $MinSlope) {
+                                $MinSlope = $Slope;
+                            }
+                        }
+
+                        if ($Y == VOID) {
+                            $LastX = null;
+                            $LastY = null;
+                        } else {
+                            $LastX = $X;
+                            $LastY = $Y;
+                        }
+                    }
+
+                    $LastX = null;
+                    $LastY = null;
+                    $LastColor = null;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($Y != VOID && $LastY != null) {
+                            $Slope = ($LastY - $Y);
+
+                            if ($Slope >= 0) {
+                                $SlopeIndex = (100 / $MaxSlope) * $Slope;
+                                $R = (($PositiveSlopeEndR - $PositiveSlopeStartR) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartR
+                                ;
+                                $G = (($PositiveSlopeEndG - $PositiveSlopeStartG) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartG
+                                ;
+                                $B = (($PositiveSlopeEndB - $PositiveSlopeStartB) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartB
+                                ;
+                            } elseif ($Slope < 0) {
+                                $SlopeIndex = (100 / abs($MinSlope)) * abs($Slope);
+                                $R = (($NegativeSlopeEndR - $NegativeSlopeStartR) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartR
+                                ;
+                                $G = (($NegativeSlopeEndG - $NegativeSlopeStartG) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartG
+                                ;
+                                $B = (($NegativeSlopeEndB - $NegativeSlopeStartB) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartB
+                                ;
+                            }
+
+                            $Color = ["R" => $R, "G" => $G, "B" => $B];
+
+                            if ($ShadedSlopeBox && $LastColor != null) {// && $Slope != 0
+                                $GradientSettings = [
+                                    "StartR" => $LastColor["R"],
+                                    "StartG" => $LastColor["G"],
+                                    "StartB" => $LastColor["B"],
+                                    "EndR" => $R,
+                                    "EndG" => $G,
+                                    "EndB" => $B
+                                ];
+                                $this->drawGradientArea(
+                                    $LastX,
+                                    $TopY,
+                                    $X,
+                                    $BottomY,
+                                    DIRECTION_HORIZONTAL,
+                                    $GradientSettings
+                                );
+                            } elseif (!$ShadedSlopeBox || $LastColor == null) { // || $Slope == 0
+                                $this->drawFilledRectangle(
+                                    floor($LastX),
+                                    $TopY,
+                                    floor($X),
+                                    $BottomY,
+                                    $Color
+                                );
+                            }
+                            $LastColor = $Color;
+                        }
+
+                        if ($Y == VOID) {
+                            $LastY = null;
+                        } else {
+                            $LastX = $X;
+                            $LastY = $Y;
+                        }
+
+                        $X = $X + $XStep;
+                    }
+
+                    $YPos = $YPos + $CaptionHeight + $SerieSpacing;
+                } else {
+                    if ($Caption) {
+                        $StartY = floor($this->GraphAreaY1 - $CaptionWidth + $XMargin - $CaptionMargin);
+                        $EndY = floor($this->GraphAreaY1 - $CaptionMargin + $XMargin);
+                        if ($CaptionLine) {
+                            $CaptionSettings = [
+                                "R" => $R,
+                                "G" => $G,
+                                "B" => $B,
+                                "Alpha" => $Alpha,
+                                "Ticks" => $Ticks,
+                                "Weight" => $Weight
+                            ];
+                            if ($CaptionBox) {
+                                $this->drawFilledRectangle(
+                                    $XPos,
+                                    $StartY,
+                                    $XPos + $CaptionHeight,
+                                    $EndY,
+                                    [
+                                        "R" => $CaptionFillR,
+                                        "G" => $CaptionFillG,
+                                        "B" => $CaptionFillB,
+                                        "BorderR" => $CaptionBorderR,
+                                        "BorderG" => $CaptionBorderG,
+                                        "BorderB" => $CaptionBorderB,
+                                        "Alpha" => $CaptionFillAlpha
+                                    ]
+                                );
+                            }
+                            $this->drawLine(
+                                $XPos + ($CaptionHeight / 2),
+                                $StartY + 2,
+                                $XPos + ($CaptionHeight / 2),
+                                $EndY - 2,
+                                $CaptionSettings
+                            );
+                        } else {
+                            $this->drawFilledRectangle(
+                                $XPos,
+                                $StartY,
+                                $XPos + $CaptionHeight,
+                                $EndY,
+                                [
+                                    "R" => $R,
+                                    "G" => $G,
+                                    "B" => $B,
+                                    "BorderR" => $CaptionBorderR,
+                                    "BorderG" => $CaptionBorderG,
+                                    "BorderB" => $CaptionBorderB
+                                ]
+                            );
+                        }
+                    }
+
+
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    $TopX = $XPos + ($CaptionHeight / 2) - ($DerivativeHeight / 2);
+                    $BottomX = $XPos + ($CaptionHeight / 2) + ($DerivativeHeight / 2);
+
+                    $StartY = floor($this->GraphAreaY1 + $XMargin);
+                    $EndY = floor($this->GraphAreaY2 - $XMargin);
+
+                    if ($DrawBackground) {
+                        $this->drawFilledRectangle(
+                            $TopX - 1,
+                            $StartY - 1,
+                            $BottomX + 1,
+                            $EndY + 1,
+                            [
+                                "R" => $BackgroundR,
+                                "G" => $BackgroundG,
+                                "B" => $BackgroundB,
+                                "Alpha" => $BackgroundAlpha
+                            ]
+                        );
+                    }
+                    if ($DrawBorder) {
+                        $this->drawRectangle(
+                            $TopX - 1,
+                            $StartY - 1,
+                            $BottomX + 1,
+                            $EndY + 1,
+                            [
+                                "R" => $BorderR,
+                                "G" => $BorderG,
+                                "B" => $BorderB,
+                                "Alpha" => $BorderAlpha
+                            ]
+                        );
+                    }
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+
+                    $RestoreShadow = $this->Shadow;
+                    $this->Shadow = false;
+
+                    /* Determine the Max slope index */
+                    $LastX = null;
+                    $LastY = null;
+                    $MinSlope = 0;
+                    $MaxSlope = 1;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($X != VOID && $LastX != null) {
+                            $Slope = ($X - $LastX);
+                            if ($Slope > $MaxSlope) {
+                                $MaxSlope = $Slope;
+                            }
+                            if ($Slope < $MinSlope) {
+                                $MinSlope = $Slope;
+                            }
+                        }
+
+                        if ($X == VOID) {
+                            $LastX = null;
+                        } else {
+                            $LastX = $X;
+                        }
+                    }
+
+                    $LastX = null;
+                    $LastY = null;
+                    $LastColor = null;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($X != VOID && $LastX != null) {
+                            $Slope = ($X - $LastX);
+
+                            if ($Slope >= 0) {
+                                $SlopeIndex = (100 / $MaxSlope) * $Slope;
+                                $R = (($PositiveSlopeEndR - $PositiveSlopeStartR) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartR
+                                ;
+                                $G = (($PositiveSlopeEndG - $PositiveSlopeStartG) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartG
+                                ;
+                                $B = (($PositiveSlopeEndB - $PositiveSlopeStartB) / 100)
+                                    * $SlopeIndex + $PositiveSlopeStartB
+                                ;
+                            } elseif ($Slope < 0) {
+                                $SlopeIndex = (100 / abs($MinSlope)) * abs($Slope);
+                                $R = (($NegativeSlopeEndR - $NegativeSlopeStartR) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartR
+                                ;
+                                $G = (($NegativeSlopeEndG - $NegativeSlopeStartG) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartG
+                                ;
+                                $B = (($NegativeSlopeEndB - $NegativeSlopeStartB) / 100)
+                                    * $SlopeIndex + $NegativeSlopeStartB
+                                ;
+                            }
+
+                            $Color = ["R" => $R, "G" => $G, "B" => $B];
+
+                            if ($ShadedSlopeBox && $LastColor != null) {
+                                $GradientSettings = [
+                                    "StartR" => $LastColor["R"],
+                                    "StartG" => $LastColor["G"],
+                                    "StartB" => $LastColor["B"],
+                                    "EndR" => $R,
+                                    "EndG" => $G,
+                                    "EndB" => $B
+                                ];
+
+                                $this->drawGradientArea(
+                                    $TopX,
+                                    $LastY,
+                                    $BottomX,
+                                    $Y,
+                                    DIRECTION_VERTICAL,
+                                    $GradientSettings
+                                );
+                            } elseif (!$ShadedSlopeBox || $LastColor == null) {
+                                $this->drawFilledRectangle(
+                                    $TopX,
+                                    floor($LastY),
+                                    $BottomX,
+                                    floor($Y),
+                                    $Color
+                                );
+                            }
+                            $LastColor = $Color;
+                        }
+
+                        if ($X == VOID) {
+                            $LastX = null;
+                        } else {
+                            $LastX = $X;
+                            $LastY = $Y;
+                        }
+
+                        $Y = $Y + $XStep;
+                    }
+
+                    $XPos = $XPos + $CaptionHeight + $SerieSpacing;
+                }
+                $this->Shadow = $RestoreShadow;
+            }
+        }
+    }
+
+    /**
+     * Draw the line of best fit
+     * @param array $Format
+     */
+    public function drawBestFit(array $Format = [])
+    {
+        $OverrideTicks = isset($Format["Ticks"]) ? $Format["Ticks"] : null;
+        $OverrideR = isset($Format["R"]) ? $Format["R"] : VOID;
+        $OverrideG = isset($Format["G"]) ? $Format["G"] : VOID;
+        $OverrideB = isset($Format["B"]) ? $Format["B"] : VOID;
+        $OverrideAlpha = isset($Format["Alpha"]) ? $Format["Alpha"] : VOID;
+
+        $Data = $this->DataSet->getData();
+        list($XMargin, $XDivs) = $this->scaleGetXSettings();
+
+        foreach ($Data["Series"] as $SerieName => $Serie) {
+            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
+                if ($OverrideR != VOID && $OverrideG != VOID && $OverrideB != VOID) {
+                    $R = $OverrideR;
+                    $G = $OverrideG;
+                    $B = $OverrideB;
+                } else {
+                    $R = $Serie["Color"]["R"];
+                    $G = $Serie["Color"]["G"];
+                    $B = $Serie["Color"]["B"];
+                }
+                if ($OverrideTicks == null) {
+                    $Ticks = $Serie["Ticks"];
+                } else {
+                    $Ticks = $OverrideTicks;
+                }
+                if ($OverrideAlpha == VOID) {
+                    $Alpha = $Serie["Color"]["Alpha"];
+                } else {
+                    $Alpha = $OverrideAlpha;
+                }
+
+                $Color = ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha, "Ticks" => $Ticks];
+
+                $PosArray = $this->scaleComputeY(
+                    $Serie["Data"],
+                    ["AxisID" => $Serie["Axis"]]
+                );
+
+                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
+                    if ($XDivs == 0) {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
+                    } else {
+                        $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
+                    }
+                    $X = $this->GraphAreaX1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $Sxy = 0;
+                    $Sx = 0;
+                    $Sy = 0;
+                    $Sxx = 0;
+                    foreach ($PosArray as $Key => $Y) {
+                        if ($Y != VOID) {
+                            $Sxy = $Sxy + $X * $Y;
+                            $Sx = $Sx + $X;
+                            $Sy = $Sy + $Y;
+                            $Sxx = $Sxx + $X * $X;
+                        }
+
+                        $X = $X + $XStep;
+                    }
+                    $n = count($this->DataSet->stripVOID($PosArray)); //$n = count($PosArray);
+                    $M = (($n * $Sxy) - ($Sx * $Sy)) / (($n * $Sxx) - ($Sx * $Sx));
+                    $B = (($Sy) - ($M * $Sx)) / ($n);
+
+                    $X1 = $this->GraphAreaX1 + $XMargin;
+                    $Y1 = $M * $X1 + $B;
+                    $X2 = $this->GraphAreaX2 - $XMargin;
+                    $Y2 = $M * $X2 + $B;
+
+                    if ($Y1 < $this->GraphAreaY1) {
+                        $X1 = $X1 + ($this->GraphAreaY1 - $Y1);
+                        $Y1 = $this->GraphAreaY1;
+                    }
+                    if ($Y1 > $this->GraphAreaY2) {
+                        $X1 = $X1 + ($Y1 - $this->GraphAreaY2);
+                        $Y1 = $this->GraphAreaY2;
+                    }
+                    if ($Y2 < $this->GraphAreaY1) {
+                        $X2 = $X2 - ($this->GraphAreaY1 - $Y2);
+                        $Y2 = $this->GraphAreaY1;
+                    }
+                    if ($Y2 > $this->GraphAreaY2) {
+                        $X2 = $X2 - ($Y2 - $this->GraphAreaY2);
+                        $Y2 = $this->GraphAreaY2;
+                    }
+
+                    $this->drawLine($X1, $Y1, $X2, $Y2, $Color);
+                } else {
+                    if ($XDivs == 0) {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
+                    } else {
+                        $YStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
+                    }
+                    $Y = $this->GraphAreaY1 + $XMargin;
+
+                    if (!is_array($PosArray)) {
+                        $Value = $PosArray;
+                        $PosArray = [];
+                        $PosArray[0] = $Value;
+                    }
+                    $Sxy = 0;
+                    $Sx = 0;
+                    $Sy = 0;
+                    $Sxx = 0;
+                    foreach ($PosArray as $Key => $X) {
+                        if ($X != VOID) {
+                            $Sxy = $Sxy + $X * $Y;
+                            $Sx = $Sx + $Y;
+                            $Sy = $Sy + $X;
+                            $Sxx = $Sxx + $Y * $Y;
+                        }
+
+                        $Y = $Y + $YStep;
+                    }
+                    $n = count($this->DataSet->stripVOID($PosArray)); //$n = count($PosArray);
+                    $M = (($n * $Sxy) - ($Sx * $Sy)) / (($n * $Sxx) - ($Sx * $Sx));
+                    $B = (($Sy) - ($M * $Sx)) / ($n);
+
+                    $Y1 = $this->GraphAreaY1 + $XMargin;
+                    $X1 = $M * $Y1 + $B;
+                    $Y2 = $this->GraphAreaY2 - $XMargin;
+                    $X2 = $M * $Y2 + $B;
+
+                    if ($X1 < $this->GraphAreaX1) {
+                        $Y1 = $Y1 + ($this->GraphAreaX1 - $X1);
+                        $X1 = $this->GraphAreaX1;
+                    }
+                    if ($X1 > $this->GraphAreaX2) {
+                        $Y1 = $Y1 + ($X1 - $this->GraphAreaX2);
+                        $X1 = $this->GraphAreaX2;
+                    }
+                    if ($X2 < $this->GraphAreaX1) {
+                        $Y2 = $Y2 - ($this->GraphAreaY1 - $X2);
+                        $X2 = $this->GraphAreaX1;
+                    }
+                    if ($X2 > $this->GraphAreaX2) {
+                        $Y2 = $Y2 - ($X2 - $this->GraphAreaX2);
+                        $X2 = $this->GraphAreaX2;
+                    }
+
+                    $this->drawLine($X1, $Y1, $X2, $Y2, $Color);
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw a label box
+     * @param int $X
+     * @param int $Y
+     * @param string $Title
+     * @param array $Captions
+     * @param array $Format
+     */
+    public function drawLabelBox($X, $Y, $Title, array $Captions, array $Format = [])
+    {
+        $NoTitle = isset($Format["NoTitle"]) ? $Format["NoTitle"] : null;
+        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 50;
+        $DrawSerieColor = isset($Format["DrawSerieColor"]) ? $Format["DrawSerieColor"] : true;
+        $SerieBoxSize = isset($Format["SerieBoxSize"]) ? $Format["SerieBoxSize"] : 6;
+        $SerieBoxSpacing = isset($Format["SerieBoxSpacing"]) ? $Format["SerieBoxSpacing"] : 4;
+        $VerticalMargin = isset($Format["VerticalMargin"]) ? $Format["VerticalMargin"] : 10;
+        $HorizontalMargin = isset($Format["HorizontalMargin"]) ? $Format["HorizontalMargin"] : 8;
+        $R = isset($Format["R"]) ? $Format["R"] : $this->FontColorR;
+        $G = isset($Format["G"]) ? $Format["G"] : $this->FontColorG;
+        $B = isset($Format["B"]) ? $Format["B"] : $this->FontColorB;
+        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
+        $TitleMode = isset($Format["TitleMode"]) ? $Format["TitleMode"] : LABEL_TITLE_NOBACKGROUND;
+        $TitleR = isset($Format["TitleR"]) ? $Format["TitleR"] : $R;
+        $TitleG = isset($Format["TitleG"]) ? $Format["TitleG"] : $G;
+        $TitleB = isset($Format["TitleB"]) ? $Format["TitleB"] : $B;
+        $TitleBackgroundR = isset($Format["TitleBackgroundR"]) ? $Format["TitleBackgroundR"] : 0;
+        $TitleBackgroundG = isset($Format["TitleBackgroundG"]) ? $Format["TitleBackgroundG"] : 0;
+        $TitleBackgroundB = isset($Format["TitleBackgroundB"]) ? $Format["TitleBackgroundB"] : 0;
+        $GradientStartR = isset($Format["GradientStartR"]) ? $Format["GradientStartR"] : 255;
+        $GradientStartG = isset($Format["GradientStartG"]) ? $Format["GradientStartG"] : 255;
+        $GradientStartB = isset($Format["GradientStartB"]) ? $Format["GradientStartB"] : 255;
+        $GradientEndR = isset($Format["GradientEndR"]) ? $Format["GradientEndR"] : 220;
+        $GradientEndG = isset($Format["GradientEndG"]) ? $Format["GradientEndG"] : 220;
+        $GradientEndB = isset($Format["GradientEndB"]) ? $Format["GradientEndB"] : 220;
+        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 100;
+
+        if (!$DrawSerieColor) {
+            $SerieBoxSize = 0;
+            $SerieBoxSpacing = 0;
+        }
+
+        $TxtPos = $this->getTextBox($X, $Y, $FontName, $FontSize, 0, $Title);
+        $TitleWidth = ($TxtPos[1]["X"] - $TxtPos[0]["X"]) + $VerticalMargin * 2;
+        $TitleHeight = ($TxtPos[0]["Y"] - $TxtPos[2]["Y"]);
+
+        if ($NoTitle) {
+            $TitleWidth = 0;
+            $TitleHeight = 0;
+        }
+
+        $CaptionWidth = 0;
+        $CaptionHeight = -$HorizontalMargin;
+        foreach ($Captions as $Key => $Caption) {
+            $TxtPos = $this->getTextBox(
+                $X,
+                $Y,
+                $FontName,
+                $FontSize,
+                0,
+                $Caption["Caption"]
+            );
+            $CaptionWidth = max(
+                $CaptionWidth,
+                ($TxtPos[1]["X"] - $TxtPos[0]["X"]) + $VerticalMargin * 2
+            );
+            $CaptionHeight = $CaptionHeight
+                + max(($TxtPos[0]["Y"] - $TxtPos[2]["Y"]), ($SerieBoxSize + 2))
+                + $HorizontalMargin
+            ;
+        }
+
+        if ($CaptionHeight <= 5) {
+            $CaptionHeight = $CaptionHeight + $HorizontalMargin / 2;
+        }
+
+        if ($DrawSerieColor) {
+            $CaptionWidth = $CaptionWidth + $SerieBoxSize + $SerieBoxSpacing;
+        }
+
+        $BoxWidth = max($BoxWidth, $TitleWidth, $CaptionWidth);
+
+        $XMin = $X - 5 - floor(($BoxWidth - 10) / 2);
+        $XMax = $X + 5 + floor(($BoxWidth - 10) / 2);
+
+        $RestoreShadow = $this->Shadow;
+        if ($this->Shadow == true) {
+            $this->Shadow = false;
+
+            $Poly = [];
+            $Poly[] = $X + $this->ShadowX;
+            $Poly[] = $Y + $this->ShadowX;
+            $Poly[] = $X + 5 + $this->ShadowX;
+            $Poly[] = $Y - 5 + $this->ShadowX;
+            $Poly[] = $XMax + $this->ShadowX;
+            $Poly[] = $Y - 5 + $this->ShadowX;
+
+            if ($NoTitle) {
+                $Poly[] = $XMax + $this->ShadowX;
+                $Poly[] = $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2 + $this->ShadowX;
+                $Poly[] = $XMin + $this->ShadowX;
+                $Poly[] = $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2 + $this->ShadowX;
+            } else {
+                $Poly[] = $XMax + $this->ShadowX;
+                $Poly[] = $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3 + $this->ShadowX;
+                $Poly[] = $XMin + $this->ShadowX;
+                $Poly[] = $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3 + $this->ShadowX;
+            }
+
+            $Poly[] = $XMin + $this->ShadowX;
+            $Poly[] = $Y - 5 + $this->ShadowX;
+            $Poly[] = $X - 5 + $this->ShadowX;
+            $Poly[] = $Y - 5 + $this->ShadowX;
+            $this->drawPolygon(
+                $Poly,
+                [
+                    "R" => $this->ShadowR,
+                    "G" => $this->ShadowG,
+                    "B" => $this->ShadowB,
+                    "Alpha" => $this->Shadowa
+                ]
+            );
+        }
+
+        /* Draw the background */
+        $GradientSettings = [
+            "StartR" => $GradientStartR,
+            "StartG" => $GradientStartG,
+            "StartB" => $GradientStartB,
+            "EndR" => $GradientEndR,
+            "EndG" => $GradientEndG,
+            "EndB" => $GradientEndB,
+            "Alpha" => $BoxAlpha
+        ];
+        if ($NoTitle) {
+            $this->drawGradientArea(
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMax,
+                $Y - 6,
+                DIRECTION_VERTICAL,
+                $GradientSettings
+            );
+        } else {
+            $this->drawGradientArea(
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax,
+                $Y - 6,
+                DIRECTION_VERTICAL,
+                $GradientSettings
+            );
+        }
+        $Poly = [];
+        $Poly[] = $X;
+        $Poly[] = $Y;
+        $Poly[] = $X - 5;
+        $Poly[] = $Y - 5;
+        $Poly[] = $X + 5;
+        $Poly[] = $Y - 5;
+        $this->drawPolygon(
+            $Poly,
+            [
+                "R" => $GradientEndR,
+                "G" => $GradientEndG,
+                "B" => $GradientEndB,
+                "Alpha" => $BoxAlpha,
+                "NoBorder" => true
+            ]
+        );
+
+        /* Outer border */
+        $OuterBorderColor = $this->allocateColor($this->Picture, 100, 100, 100, $BoxAlpha);
+        imageline($this->Picture, $XMin, $Y - 5, $X - 5, $Y - 5, $OuterBorderColor);
+        imageline($this->Picture, $X, $Y, $X - 5, $Y - 5, $OuterBorderColor);
+        imageline($this->Picture, $X, $Y, $X + 5, $Y - 5, $OuterBorderColor);
+        imageline($this->Picture, $X + 5, $Y - 5, $XMax, $Y - 5, $OuterBorderColor);
+        if ($NoTitle) {
+            imageline(
+                $this->Picture,
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMin,
+                $Y - 5,
+                $OuterBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMax,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMax,
+                $Y - 5,
+                $OuterBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMax,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $OuterBorderColor
+            );
+        } else {
+            imageline(
+                $this->Picture,
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMin,
+                $Y - 5,
+                $OuterBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMax,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax,
+                $Y - 5,
+                $OuterBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $OuterBorderColor
+            );
+        }
+
+        /* Inner border */
+        $InnerBorderColor = $this->allocateColor($this->Picture, 255, 255, 255, $BoxAlpha);
+        imageline($this->Picture, $XMin + 1, $Y - 6, $X - 5, $Y - 6, $InnerBorderColor);
+        imageline($this->Picture, $X, $Y - 1, $X - 5, $Y - 6, $InnerBorderColor);
+        imageline($this->Picture, $X, $Y - 1, $X + 5, $Y - 6, $InnerBorderColor);
+        imageline($this->Picture, $X + 5, $Y - 6, $XMax - 1, $Y - 6, $InnerBorderColor);
+        if ($NoTitle) {
+            imageline(
+                $this->Picture,
+                $XMin + 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMin + 1,
+                $Y - 6,
+                $InnerBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMax - 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMax - 1,
+                $Y - 6,
+                $InnerBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMin + 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $XMax - 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 2,
+                $InnerBorderColor
+            );
+        } else {
+            imageline(
+                $this->Picture,
+                $XMin + 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMin + 1,
+                $Y - 6,
+                $InnerBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMax - 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax - 1,
+                $Y - 6,
+                $InnerBorderColor
+            );
+            imageline(
+                $this->Picture,
+                $XMin + 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax - 1,
+                $Y - 4 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $InnerBorderColor
+            );
+        }
+
+        /* Draw the separator line */
+        if ($TitleMode == LABEL_TITLE_NOBACKGROUND && !$NoTitle) {
+            $YPos = $Y - 7 - $CaptionHeight - $HorizontalMargin - $HorizontalMargin / 2;
+            $XMargin = $VerticalMargin / 2;
+            $this->drawLine(
+                $XMin + $XMargin,
+                $YPos + 1,
+                $XMax - $XMargin,
+                $YPos + 1,
+                [
+                    "R" => $GradientEndR,
+                    "G" => $GradientEndG,
+                    "B" => $GradientEndB,
+                    "Alpha" => $BoxAlpha
+                ]
+            );
+            $this->drawLine(
+                $XMin + $XMargin,
+                $YPos,
+                $XMax - $XMargin,
+                $YPos,
+                [
+                    "R" => $GradientStartR,
+                    "G" => $GradientStartG,
+                    "B" => $GradientStartB,
+                    "Alpha" => $BoxAlpha
+                ]
+            );
+        } elseif ($TitleMode == LABEL_TITLE_BACKGROUND) {
+            $this->drawFilledRectangle(
+                $XMin,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin * 3,
+                $XMax,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin + $HorizontalMargin / 2,
+                [
+                    "R" => $TitleBackgroundR,
+                    "G" => $TitleBackgroundG,
+                    "B" => $TitleBackgroundB,
+                    "Alpha" => $BoxAlpha
+                ]
+            );
+            imageline(
+                $this->Picture,
+                $XMin + 1,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin + $HorizontalMargin / 2 + 1,
+                $XMax - 1,
+                $Y - 5 - $TitleHeight - $CaptionHeight - $HorizontalMargin + $HorizontalMargin / 2 + 1,
+                $InnerBorderColor
+            );
+        }
+
+        /* Write the description */
+        if (!$NoTitle) {
+            $this->drawText(
+                $XMin + $VerticalMargin,
+                $Y - 7 - $CaptionHeight - $HorizontalMargin * 2,
+                $Title,
+                [
+                    "Align" => TEXT_ALIGN_BOTTOMLEFT,
+                    "R" => $TitleR,
+                    "G" => $TitleG,
+                    "B" => $TitleB
+                ]
+            );
+        }
+
+        /* Write the value */
+        $YPos = $Y - 5 - $HorizontalMargin;
+        $XPos = $XMin + $VerticalMargin + $SerieBoxSize + $SerieBoxSpacing;
+        foreach ($Captions as $Key => $Caption) {
+            $CaptionTxt = $Caption["Caption"];
+            $TxtPos = $this->getTextBox($XPos, $YPos, $FontName, $FontSize, 0, $CaptionTxt);
+            $CaptionHeight = ($TxtPos[0]["Y"] - $TxtPos[2]["Y"]);
+
+            /* Write the serie color if needed */
+            if ($DrawSerieColor) {
+                $BoxSettings = [
+                    "R" => $Caption["Format"]["R"],
+                    "G" => $Caption["Format"]["G"],
+                    "B" => $Caption["Format"]["B"],
+                    "Alpha" => $Caption["Format"]["Alpha"],
+                    "BorderR" => 0,
+                    "BorderG" => 0,
+                    "BorderB" => 0
+                ];
+                $this->drawFilledRectangle(
+                    $XMin + $VerticalMargin,
+                    $YPos - $SerieBoxSize,
+                    $XMin + $VerticalMargin + $SerieBoxSize,
+                    $YPos,
+                    $BoxSettings
+                );
+            }
+
+            $this->drawText($XPos, $YPos, $CaptionTxt, ["Align" => TEXT_ALIGN_BOTTOMLEFT]);
+
+            $YPos = $YPos - $CaptionHeight - $HorizontalMargin;
+        }
+
+        $this->Shadow = $RestoreShadow;
+    }
+
+    /**
+     * Draw a basic shape
+     * @param int $X
+     * @param int $Y
+     * @param int $Shape
+     * @param int $PlotSize
+     * @param int $PlotBorder
+     * @param int $BorderSize
+     * @param int $R
+     * @param int $G
+     * @param int $B
+     * @param int|float $Alpha
+     * @param int $BorderR
+     * @param int $BorderG
+     * @param int $BorderB
+     * @param int|float $BorderAlpha
+     */
+    public function drawShape(
+        $X,
+        $Y,
+        $Shape,
+        $PlotSize,
+        $PlotBorder,
+        $BorderSize,
+        $R,
+        $G,
+        $B,
+        $Alpha,
+        $BorderR,
+        $BorderG,
+        $BorderB,
+        $BorderAlpha
+    ) {
+        if ($Shape == SERIE_SHAPE_FILLEDCIRCLE) {
+            if ($PlotBorder) {
+                $this->drawFilledCircle(
+                    $X,
+                    $Y,
+                    $PlotSize + $BorderSize,
+                    ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $BorderAlpha]
+                );
+            }
+            $this->drawFilledCircle(
+                $X,
+                $Y,
+                $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+        } elseif ($Shape == SERIE_SHAPE_FILLEDSQUARE) {
+            if ($PlotBorder) {
+                $this->drawFilledRectangle(
+                    $X - $PlotSize - $BorderSize,
+                    $Y - $PlotSize - $BorderSize,
+                    $X + $PlotSize + $BorderSize,
+                    $Y + $PlotSize + $BorderSize,
+                    ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $BorderAlpha]
+                );
+            }
+            $this->drawFilledRectangle(
+                $X - $PlotSize,
+                $Y - $PlotSize,
+                $X + $PlotSize,
+                $Y + $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+        } elseif ($Shape == SERIE_SHAPE_FILLEDTRIANGLE) {
+            if ($PlotBorder) {
+                $Pos = [];
+                $Pos[] = $X;
+                $Pos[] = $Y - $PlotSize - $BorderSize;
+                $Pos[] = $X - $PlotSize - $BorderSize;
+                $Pos[] = $Y + $PlotSize + $BorderSize;
+                $Pos[] = $X + $PlotSize + $BorderSize;
+                $Pos[] = $Y + $PlotSize + $BorderSize;
+                $this->drawPolygon(
+                    $Pos,
+                    ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $BorderAlpha]
+                );
+            }
+
+            $Pos = [];
+            $Pos[] = $X;
+            $Pos[] = $Y - $PlotSize;
+            $Pos[] = $X - $PlotSize;
+            $Pos[] = $Y + $PlotSize;
+            $Pos[] = $X + $PlotSize;
+            $Pos[] = $Y + $PlotSize;
+            $this->drawPolygon($Pos, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+        } elseif ($Shape == SERIE_SHAPE_TRIANGLE) {
+            $this->drawLine(
+                $X,
+                $Y - $PlotSize,
+                $X - $PlotSize,
+                $Y + $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+            $this->drawLine(
+                $X - $PlotSize,
+                $Y + $PlotSize,
+                $X + $PlotSize,
+                $Y + $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+            $this->drawLine(
+                $X + $PlotSize,
+                $Y + $PlotSize,
+                $X,
+                $Y - $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+        } elseif ($Shape == SERIE_SHAPE_SQUARE) {
+            $this->drawRectangle(
+                $X - $PlotSize,
+                $Y - $PlotSize,
+                $X + $PlotSize,
+                $Y + $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+        } elseif ($Shape == SERIE_SHAPE_CIRCLE) {
+            $this->drawCircle(
+                $X,
+                $Y,
+                $PlotSize,
+                $PlotSize,
+                ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]
+            );
+        } elseif ($Shape == SERIE_SHAPE_DIAMOND) {
+            $Pos = [];
+            $Pos[] = $X - $PlotSize;
+            $Pos[] = $Y;
+            $Pos[] = $X;
+            $Pos[] = $Y - $PlotSize;
+            $Pos[] = $X + $PlotSize;
+            $Pos[] = $Y;
+            $Pos[] = $X;
+            $Pos[] = $Y + $PlotSize;
+            $this->drawPolygon(
+                $Pos,
+                [
+                    "NoFill" => true,
+                    "BorderR" => $R,
+                    "BorderG" => $G,
+                    "BorderB" => $B,
+                    "BorderAlpha" => $Alpha
+                ]
+            );
+        } elseif ($Shape == SERIE_SHAPE_FILLEDDIAMOND) {
+            if ($PlotBorder) {
+                $Pos = [];
+                $Pos[] = $X - $PlotSize - $BorderSize;
+                $Pos[] = $Y;
+                $Pos[] = $X;
+                $Pos[] = $Y - $PlotSize - $BorderSize;
+                $Pos[] = $X + $PlotSize + $BorderSize;
+                $Pos[] = $Y;
+                $Pos[] = $X;
+                $Pos[] = $Y + $PlotSize + $BorderSize;
+                $this->drawPolygon(
+                    $Pos,
+                    ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB, "Alpha" => $BorderAlpha]
+                );
+            }
+
+            $Pos = [];
+            $Pos[] = $X - $PlotSize;
+            $Pos[] = $Y;
+            $Pos[] = $X;
+            $Pos[] = $Y - $PlotSize;
+            $Pos[] = $X + $PlotSize;
+            $Pos[] = $Y;
+            $Pos[] = $X;
+            $Pos[] = $Y + $PlotSize;
+            $this->drawPolygon($Pos, ["R" => $R, "G" => $G, "B" => $B, "Alpha" => $Alpha]);
+        }
+    }
+
+    /**
+     *
+     * @param array $Points
+     * @param array $Format
+     * @return null|integer
+     */
+    public function drawPolygonChart(array $Points, array $Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $NoFill = isset($Format["NoFill"]) ? $Format["NoFill"] : false;
+        $NoBorder = isset($Format["NoBorder"]) ? $Format["NoBorder"] : false;
+        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : $R;
+        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : $G;
+        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : $B;
+        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : $Alpha / 2;
+        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
+        $Threshold = isset($Format["Threshold"]) ? $Format["Threshold"] : null;
+
+        if ($Surrounding != null) {
+            $BorderR = $R + $Surrounding;
+            $BorderG = $G + $Surrounding;
+            $BorderB = $B + $Surrounding;
+        }
+
+        $RestoreShadow = $this->Shadow;
+        $this->Shadow = false;
+
+        $AllIntegers = true;
+        for ($i = 0; $i <= count($Points) - 2; $i = $i + 2) {
+            if ($this->getFirstDecimal($Points[$i + 1]) != 0) {
+                $AllIntegers = false;
+            }
+        }
+
+        /* Convert polygon to segments */
+        $Segments = [];
+        for ($i = 2; $i <= count($Points) - 2; $i = $i + 2) {
+            $Segments[] = [
+                "X1" => $Points[$i - 2],
+                "Y1" => $Points[$i - 1],
+                "X2" => $Points[$i],
+                "Y2" => $Points[$i + 1]
+            ];
+        }
+        $Segments[] = [
+            "X1" => $Points[$i - 2],
+            "Y1" => $Points[$i - 1],
+            "X2" => $Points[0],
+            "Y2" => $Points[1]
+        ];
+
+        /* Simplify straight lines */
+        $Result = [];
+        $inHorizon = false;
+        $LastX = VOID;
+        foreach ($Segments as $Key => $Pos) {
+            if ($Pos["Y1"] != $Pos["Y2"]) {
+                if ($inHorizon) {
+                    $inHorizon = false;
+                    $Result[] = [
+                        "X1" => $LastX,
+                        "Y1" => $Pos["Y1"],
+                        "X2" => $Pos["X1"],
+                        "Y2" => $Pos["Y1"]
+                    ];
+                }
+
+                $Result[] = [
+                    "X1" => $Pos["X1"],
+                    "Y1" => $Pos["Y1"],
+                    "X2" => $Pos["X2"],
+                    "Y2" => $Pos["Y2"]
+                ];
+            } else {
+                if (!$inHorizon) {
+                    $inHorizon = true;
+                    $LastX = $Pos["X1"];
+                }
+            }
+        }
+        $Segments = $Result;
+
+        /* Do we have something to draw */
+        if (!count($Segments)) {
+            return 0;
+        }
+
+        /* For segments debugging purpose */
+        //foreach($Segments as $Key => $Pos)
+        // echo $Pos["X1"].",".$Pos["Y1"].",".$Pos["X2"].",".$Pos["Y2"]."\r\n";
+
+        /* Find out the min & max Y boundaries */
+        $MinY = OUT_OF_SIGHT;
+        $MaxY = OUT_OF_SIGHT;
+        foreach ($Segments as $Key => $Coords) {
+            if ($MinY == OUT_OF_SIGHT || $MinY > min($Coords["Y1"], $Coords["Y2"])) {
+                $MinY = min($Coords["Y1"], $Coords["Y2"]);
+            }
+            if ($MaxY == OUT_OF_SIGHT || $MaxY < max($Coords["Y1"], $Coords["Y2"])) {
+                $MaxY = max($Coords["Y1"], $Coords["Y2"]);
+            }
+        }
+
+        if ($AllIntegers) {
+            $YStep = 1;
+        } else {
+            $YStep = .5;
+        }
+
+        $MinY = floor($MinY);
+        $MaxY = floor($MaxY);
+
+        /* Scan each Y lines */
+        $DefaultColor = $this->allocateColor($this->Picture, $R, $G, $B, $Alpha);
+        $DebugLine = 0;
+        $DebugColor = $this->allocateColor($this->Picture, 255, 0, 0, 100);
+
+        $MinY = floor($MinY);
+        $MaxY = floor($MaxY);
+        $YStep = 1;
+
+        if (!$NoFill) {
+            //if ($DebugLine ) { $MinY = $DebugLine; $MaxY = $DebugLine; }
+            for ($Y = $MinY; $Y <= $MaxY; $Y = $Y + $YStep) {
+                $Intersections = [];
+                $LastSlope = null;
+                $RestoreLast = "-";
+                foreach ($Segments as $Key => $Coords) {
+                    $X1 = $Coords["X1"];
+                    $X2 = $Coords["X2"];
+                    $Y1 = $Coords["Y1"];
+                    $Y2 = $Coords["Y2"];
+
+                    if (min($Y1, $Y2) <= $Y && max($Y1, $Y2) >= $Y) {
+                        if ($Y1 == $Y2) {
+                            $X = $X1;
+                        } else {
+                            $X = $X1 + (($Y - $Y1) * $X2 - ($Y - $Y1) * $X1) / ($Y2 - $Y1);
+                        }
+
+                        $X = floor($X);
+
+                        if ($X2 == $X1) {
+                            $Slope = "!";
+                        } else {
+                            $SlopeC = ($Y2 - $Y1) / ($X2 - $X1);
+                            if ($SlopeC == 0) {
+                                $Slope = "=";
+                            } elseif ($SlopeC > 0) {
+                                $Slope = "+";
+                            } elseif ($SlopeC < 0) {
+                                $Slope = "-";
+                            }
+                        }
+
+                        if (!is_array($Intersections)) {
+                            $Intersections[] = $X;
+                        } elseif (!in_array($X, $Intersections)) {
+                            $Intersections[] = $X;
+                        } elseif (in_array($X, $Intersections)) {
+                            if ($Y == $DebugLine) {
+                                echo $Slope . "/" . $LastSlope . "(" . $X . ") ";
+                            }
+
+                            if ($Slope == "=" && $LastSlope == "-") {
+                                $Intersections[] = $X;
+                            }
+                            if ($Slope != $LastSlope && $LastSlope != "!" && $LastSlope != "=") {
+                                $Intersections[] = $X;
+                            }
+                            if ($Slope != $LastSlope && $LastSlope == "!" && $Slope == "+") {
+                                $Intersections[] = $X;
+                            }
+                        }
+
+                        if (is_array($Intersections)
+                            && in_array($X, $Intersections)
+                            && $LastSlope == "="
+                            && ($Slope == "-")
+                        ) {
+                            $Intersections[] = $X;
+                        }
+
+                        $LastSlope = $Slope;
+                    }
+                }
+                if ($RestoreLast != "-") {
+                    $Intersections[] = $RestoreLast;
+                    echo "@" . $Y . "\r\n";
+                }
+
+                if (is_array($Intersections)) {
+                    sort($Intersections);
+
+                    if ($Y == $DebugLine) {
+                        print_r($Intersections);
+                    }
+
+                    /* Remove null plots */
+                    $Result = [];
+                    for ($i = 0; $i <= count($Intersections) - 1; $i = $i + 2) {
+                        if (isset($Intersections[$i + 1])) {
+                            if ($Intersections[$i] != $Intersections[$i + 1]) {
+                                $Result[] = $Intersections[$i];
+                                $Result[] = $Intersections[$i + 1];
+                            }
+                        }
+                    }
+
+                    if (is_array($Result)) {
+                        $Intersections = $Result;
+
+                        $LastX = OUT_OF_SIGHT;
+                        foreach ($Intersections as $Key => $X) {
+                            if ($LastX == OUT_OF_SIGHT) {
+                                $LastX = $X;
+                            } elseif ($LastX != OUT_OF_SIGHT) {
+                                if ($this->getFirstDecimal($LastX) > 1) {
+                                    $LastX++;
+                                }
+
+                                $Color = $DefaultColor;
+                                if ($Threshold != null) {
+                                    foreach ($Threshold as $Key => $Parameters) {
+                                        if ($Y <= $Parameters["MinX"]
+                                            && $Y >= $Parameters["MaxX"]
+                                        ) {
+                                            if (isset($Parameters["R"])) {
+                                                $R = $Parameters["R"];
+                                            } else {
+                                                $R = 0;
+                                            }
+                                            if (isset($Parameters["G"])) {
+                                                $G = $Parameters["G"];
+                                            } else {
+                                                $G = 0;
+                                            }
+                                            if (isset($Parameters["B"])) {
+                                                $B = $Parameters["B"];
+                                            } else {
+                                                $B = 0;
+                                            }
+                                            if (isset($Parameters["Alpha"])) {
+                                                $Alpha = $Parameters["Alpha"];
+                                            } else {
+                                                $Alpha = 100;
+                                            }
+                                            $Color = $this->allocateColor(
+                                                $this->Picture,
+                                                $R,
+                                                $G,
+                                                $B,
+                                                $Alpha
+                                            );
+                                        }
+                                    }
+                                }
+
+                                imageline($this->Picture, $LastX, $Y, $X, $Y, $Color);
+
+                                if ($Y == $DebugLine) {
+                                    imageline($this->Picture, $LastX, $Y, $X, $Y, $DebugColor);
+                                }
+
+                                $LastX = OUT_OF_SIGHT;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /* Draw the polygon border, if required */
+        if (!$NoBorder) {
+            foreach ($Segments as $Key => $Coords) {
+                $this->drawLine(
+                    $Coords["X1"],
+                    $Coords["Y1"],
+                    $Coords["X2"],
+                    $Coords["Y2"],
+                    [
+                        "R" => $BorderR,
+                        "G" => $BorderG,
+                        "B" => $BorderB,
+                        "Alpha" => $BorderAlpha,
+                        "Threshold" => $Threshold
+                    ]
+                );
+            }
+        }
+
+        $this->Shadow = $RestoreShadow;
+    }
+}

+ 748 - 0
vendor/szymach/c-pchart/src/Image.php

@@ -0,0 +1,748 @@
+<?php
+
+namespace CpChart;
+
+/**
+ * Image - The actual class to do most of the drawing. Extends the Draw class
+ * with the bulk of drawing methods.
+ *
+ * Version     : 2.1.4
+ * Made by     : Jean-Damien POGOLOTTI
+ * Last Update : 19/01/2014
+ *
+ * This file can be distributed under the license you can find at :
+ *
+ *                http://www.pchart.net/license
+ *
+ * You can find the whole class documentation on the pChart web site.
+ */
+class Image extends Draw
+{
+    /**
+     * @param int $XSize
+     * @param int $YSize
+     * @param Data $DataSet
+     * @param boolean $TransparentBackground
+     */
+    public function __construct(
+        $XSize,
+        $YSize,
+        Data $DataSet = null,
+        $TransparentBackground = false
+    ) {
+        parent::__construct();
+
+        $this->TransparentBackground = $TransparentBackground;
+
+        $this->DataSet = null !== $DataSet ? $DataSet : new Data();
+        $this->XSize = $XSize;
+        $this->YSize = $YSize;
+        $this->Picture = imagecreatetruecolor($XSize, $YSize);
+
+        if ($this->TransparentBackground) {
+            imagealphablending($this->Picture, false);
+            imagefilledrectangle(
+                $this->Picture,
+                0,
+                0,
+                $XSize,
+                $YSize,
+                imagecolorallocatealpha($this->Picture, 255, 255, 255, 127)
+            );
+            imagealphablending($this->Picture, true);
+            imagesavealpha($this->Picture, true);
+        } else {
+            $C_White = $this->AllocateColor($this->Picture, 255, 255, 255);
+            imagefilledrectangle($this->Picture, 0, 0, $XSize, $YSize, $C_White);
+        }
+    }
+
+    /**
+     * Enable / Disable and set shadow properties
+     * @param boolean $Enabled
+     * @param array $Format
+     */
+    public function setShadow($Enabled = true, array $Format = [])
+    {
+        $X = isset($Format["X"]) ? $Format["X"] : 2;
+        $Y = isset($Format["Y"]) ? $Format["Y"] : 2;
+        $R = isset($Format["R"]) ? $Format["R"] : 0;
+        $G = isset($Format["G"]) ? $Format["G"] : 0;
+        $B = isset($Format["B"]) ? $Format["B"] : 0;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 10;
+
+        $this->Shadow = $Enabled;
+        $this->ShadowX = $X;
+        $this->ShadowY = $Y;
+        $this->ShadowR = $R;
+        $this->ShadowG = $G;
+        $this->ShadowB = $B;
+        $this->Shadowa = $Alpha;
+    }
+
+    /**
+     * Set the graph area position
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @return int|null
+     */
+    public function setGraphArea($X1, $Y1, $X2, $Y2)
+    {
+        if ($X2 < $X1 || $X1 == $X2 || $Y2 < $Y1 || $Y1 == $Y2) {
+            return -1;
+        }
+
+        $this->GraphAreaX1 = $X1;
+        $this->DataSet->Data["GraphArea"]["X1"] = $X1;
+        $this->GraphAreaY1 = $Y1;
+        $this->DataSet->Data["GraphArea"]["Y1"] = $Y1;
+        $this->GraphAreaX2 = $X2;
+        $this->DataSet->Data["GraphArea"]["X2"] = $X2;
+        $this->GraphAreaY2 = $Y2;
+        $this->DataSet->Data["GraphArea"]["Y2"] = $Y2;
+    }
+
+    /**
+     * Return the width of the picture
+     * @return int
+     */
+    public function getWidth()
+    {
+        return $this->XSize;
+    }
+
+    /**
+     * Return the heigth of the picture
+     * @return int
+     */
+    public function getHeight()
+    {
+        return $this->YSize;
+    }
+
+    /**
+     * Render the picture to a file
+     * @param string $FileName
+     */
+    public function render($FileName)
+    {
+        if ($this->TransparentBackground) {
+            imagealphablending($this->Picture, false);
+            imagesavealpha($this->Picture, true);
+        }
+        imagepng($this->Picture, $FileName);
+    }
+
+    public function __toString()
+    {
+        if ($this->TransparentBackground) {
+            imagealphablending($this->Picture, false);
+            imagesavealpha($this->Picture, true);
+        }
+
+        ob_start();
+        imagepng($this->Picture);
+        return ob_get_clean();
+    }
+
+    public function toDataURI()
+    {
+        return 'data:image/png;base64,' . base64_encode($this->__toString());
+    }
+
+    /**
+     * Render the picture to a web browser stream
+     * @param boolean $BrowserExpire
+     */
+    public function stroke($BrowserExpire = false)
+    {
+        if ($this->TransparentBackground) {
+            imagealphablending($this->Picture, false);
+            imagesavealpha($this->Picture, true);
+        }
+
+        if ($BrowserExpire) {
+            header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+            header("Cache-Control: no-cache");
+            header("Pragma: no-cache");
+        }
+
+        header('Content-type: image/png');
+        imagepng($this->Picture);
+    }
+
+    /**
+     * Automatic output method based on the calling interface
+     * @param string $FileName
+     */
+    public function autoOutput($FileName = "output.png")
+    {
+        if (php_sapi_name() == "cli") {
+            $this->Render($FileName);
+        } else {
+            $this->Stroke();
+        }
+    }
+
+    /**
+     * Return the length between two points
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @return float
+     */
+    public function getLength($X1, $Y1, $X2, $Y2)
+    {
+        return sqrt(
+            pow(max($X1, $X2) - min($X1, $X2), 2) + pow(max($Y1, $Y2) - min($Y1, $Y2), 2)
+        );
+    }
+
+    /**
+     * Return the orientation of a line
+     * @param int $X1
+     * @param int $Y1
+     * @param int $X2
+     * @param int $Y2
+     * @return int
+     */
+    public function getAngle($X1, $Y1, $X2, $Y2)
+    {
+        $Opposite = $Y2 - $Y1;
+        $Adjacent = $X2 - $X1;
+        $Angle = rad2deg(atan2($Opposite, $Adjacent));
+        if ($Angle > 0) {
+            return $Angle;
+        } else {
+            return 360 - abs($Angle);
+        }
+    }
+
+    /**
+     * Return the surrounding box of text area
+     * @param int $X
+     * @param int $Y
+     * @param string $FontName
+     * @param int $FontSize
+     * @param int $Angle
+     * @param int $Text
+     * @return array
+     */
+    public function getTextBox($X, $Y, $FontName, $FontSize, $Angle, $Text)
+    {
+        $coords = imagettfbbox($FontSize, 0, $this->loadFont($FontName, 'fonts'), $Text);
+
+        $a = deg2rad($Angle);
+        $ca = cos($a);
+        $sa = sin($a);
+        $RealPos = [];
+        for ($i = 0; $i < 7; $i += 2) {
+            $RealPos[$i / 2]["X"] = $X + round($coords[$i] * $ca + $coords[$i + 1] * $sa);
+            $RealPos[$i / 2]["Y"] = $Y + round($coords[$i + 1] * $ca - $coords[$i] * $sa);
+        }
+
+        $RealPos[TEXT_ALIGN_BOTTOMLEFT]["X"] = $RealPos[0]["X"];
+        $RealPos[TEXT_ALIGN_BOTTOMLEFT]["Y"] = $RealPos[0]["Y"];
+        $RealPos[TEXT_ALIGN_BOTTOMRIGHT]["X"] = $RealPos[1]["X"];
+        $RealPos[TEXT_ALIGN_BOTTOMRIGHT]["Y"] = $RealPos[1]["Y"];
+        $RealPos[TEXT_ALIGN_TOPLEFT]["X"] = $RealPos[3]["X"];
+        $RealPos[TEXT_ALIGN_TOPLEFT]["Y"] = $RealPos[3]["Y"];
+        $RealPos[TEXT_ALIGN_TOPRIGHT]["X"] = $RealPos[2]["X"];
+        $RealPos[TEXT_ALIGN_TOPRIGHT]["Y"] = $RealPos[2]["Y"];
+        $RealPos[TEXT_ALIGN_BOTTOMMIDDLE]["X"] = ($RealPos[1]["X"] - $RealPos[0]["X"]) / 2 + $RealPos[0]["X"];
+        $RealPos[TEXT_ALIGN_BOTTOMMIDDLE]["Y"] = ($RealPos[0]["Y"] - $RealPos[1]["Y"]) / 2 + $RealPos[1]["Y"];
+        $RealPos[TEXT_ALIGN_TOPMIDDLE]["X"] = ($RealPos[2]["X"] - $RealPos[3]["X"]) / 2 + $RealPos[3]["X"];
+        $RealPos[TEXT_ALIGN_TOPMIDDLE]["Y"] = ($RealPos[3]["Y"] - $RealPos[2]["Y"]) / 2 + $RealPos[2]["Y"];
+        $RealPos[TEXT_ALIGN_MIDDLELEFT]["X"] = ($RealPos[0]["X"] - $RealPos[3]["X"]) / 2 + $RealPos[3]["X"];
+        $RealPos[TEXT_ALIGN_MIDDLELEFT]["Y"] = ($RealPos[0]["Y"] - $RealPos[3]["Y"]) / 2 + $RealPos[3]["Y"];
+        $RealPos[TEXT_ALIGN_MIDDLERIGHT]["X"] = ($RealPos[1]["X"] - $RealPos[2]["X"]) / 2 + $RealPos[2]["X"];
+        $RealPos[TEXT_ALIGN_MIDDLERIGHT]["Y"] = ($RealPos[1]["Y"] - $RealPos[2]["Y"]) / 2 + $RealPos[2]["Y"];
+        $RealPos[TEXT_ALIGN_MIDDLEMIDDLE]["X"] = ($RealPos[1]["X"] - $RealPos[3]["X"]) / 2 + $RealPos[3]["X"];
+        $RealPos[TEXT_ALIGN_MIDDLEMIDDLE]["Y"] = ($RealPos[0]["Y"] - $RealPos[2]["Y"]) / 2 + $RealPos[2]["Y"];
+
+        return $RealPos;
+    }
+
+    /**
+     * Set current font properties
+     * @param array $Format
+     */
+    public function setFontProperties($Format = [])
+    {
+        $R = isset($Format["R"]) ? $Format["R"] : -1;
+        $G = isset($Format["G"]) ? $Format["G"] : -1;
+        $B = isset($Format["B"]) ? $Format["B"] : -1;
+        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
+        $FontName = isset($Format["FontName"]) ? $Format["FontName"] : null;
+        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : null;
+
+        if ($R != -1) {
+            $this->FontColorR = $R;
+        }
+        if ($G != -1) {
+            $this->FontColorG = $G;
+        }
+        if ($B != -1) {
+            $this->FontColorB = $B;
+        }
+        if ($Alpha != null) {
+            $this->FontColorA = $Alpha;
+        }
+
+        if ($FontName != null) {
+            $this->FontName = $this->loadFont($FontName, 'fonts');
+        }
+        if ($FontSize != null) {
+            $this->FontSize = $FontSize;
+        }
+    }
+
+    /**
+     * Returns the 1st decimal values (used to correct AA bugs)
+     * @param mixed $Value
+     * @return mixed
+     */
+    public function getFirstDecimal($Value)
+    {
+        $Values = preg_split("/\./", $Value);
+        if (isset($Values[1])) {
+            return substr($Values[1], 0, 1);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Attach a dataset to your pChart Object
+     * @param Data $DataSet
+     */
+    public function setDataSet(Data $DataSet)
+    {
+        $this->DataSet = $DataSet;
+    }
+
+    /**
+     * Print attached dataset contents to STDOUT
+     */
+    public function printDataSet()
+    {
+        print_r($this->DataSet);
+    }
+
+    /**
+     * Initialise the image map methods
+     * @param string $Name
+     * @param int $StorageMode
+     * @param string $UniqueID
+     * @param string $StorageFolder
+     */
+    public function initialiseImageMap(
+        $Name = "pChart",
+        $StorageMode = IMAGE_MAP_STORAGE_SESSION,
+        $UniqueID = "imageMap",
+        $StorageFolder = "tmp"
+    ) {
+        $this->ImageMapIndex = $Name;
+        $this->ImageMapStorageMode = $StorageMode;
+
+        if ($StorageMode == IMAGE_MAP_STORAGE_SESSION) {
+            if (!isset($_SESSION)) {
+                session_start();
+            }
+            $_SESSION[$this->ImageMapIndex] = null;
+        } elseif ($StorageMode == IMAGE_MAP_STORAGE_FILE) {
+            $this->ImageMapFileName = $UniqueID;
+            $this->ImageMapStorageFolder = $StorageFolder;
+
+            $path = sprintf("%s/%s.map", $StorageFolder, $UniqueID);
+            if (file_exists($path)) {
+                unlink($path);
+            }
+        }
+    }
+
+    /**
+     * Add a zone to the image map
+     *
+     * @param string $Type
+     * @param string $Plots
+     * @param string|null $Color
+     * @param string $Title
+     * @param string $Message
+     * @param boolean $HTMLEncode
+     */
+    public function addToImageMap(
+        $Type,
+        $Plots,
+        $Color = null,
+        $Title = null,
+        $Message = null,
+        $HTMLEncode = false
+    ) {
+        if ($this->ImageMapStorageMode == null) {
+            $this->initialiseImageMap();
+        }
+
+        /* Encode the characters in the imagemap in HTML standards */
+        $Title = htmlentities(
+            str_replace("&#8364;", "\u20AC", $Title),
+            ENT_QUOTES,
+            "ISO-8859-15"
+        );
+        if ($HTMLEncode) {
+            $Message = str_replace(
+                "&gt;",
+                ">",
+                str_replace(
+                    "&lt;",
+                    "<",
+                    htmlentities($Message, ENT_QUOTES, "ISO-8859-15")
+                )
+            );
+        }
+
+        if ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_SESSION) {
+            if (!isset($_SESSION)) {
+                $this->initialiseImageMap();
+            }
+            $_SESSION[$this->ImageMapIndex][] = [$Type, $Plots, $Color, $Title, $Message];
+        } elseif ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_FILE) {
+            $Handle = fopen(
+                sprintf("%s/%s.map", $this->ImageMapStorageFolder, $this->ImageMapFileName),
+                'a'
+            );
+            fwrite(
+                $Handle,
+                sprintf(
+                    "%s%s%s%s%s%s%s%s%s\r\n",
+                    $Type,
+                    IMAGE_MAP_DELIMITER,
+                    $Plots,
+                    IMAGE_MAP_DELIMITER,
+                    $Color,
+                    IMAGE_MAP_DELIMITER,
+                    $Title,
+                    IMAGE_MAP_DELIMITER,
+                    $Message
+                )
+            );
+            fclose($Handle);
+        }
+    }
+
+    /**
+     * Remove VOID values from an imagemap custom values array
+     * @param string $SerieName
+     * @param array $Values
+     * @return array
+     */
+    public function removeVOIDFromArray($SerieName, array $Values)
+    {
+        if (!isset($this->DataSet->Data["Series"][$SerieName])) {
+            return -1;
+        }
+
+        $Result = [];
+        foreach ($this->DataSet->Data["Series"][$SerieName]["Data"] as $Key => $Value) {
+            if ($Value != VOID && isset($Values[$Key])) {
+                $Result[] = $Values[$Key];
+            }
+        }
+        return $Result;
+    }
+
+    /**
+     * Replace the title of one image map serie
+     * @param string $OldTitle
+     * @param string|array $NewTitle
+     * @return null|int
+     */
+    public function replaceImageMapTitle($OldTitle, $NewTitle)
+    {
+        if ($this->ImageMapStorageMode == null) {
+            return -1;
+        }
+
+        if (is_array($NewTitle)) {
+            $NewTitle = $this->removeVOIDFromArray($OldTitle, $NewTitle);
+        }
+
+        if ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_SESSION) {
+            if (!isset($_SESSION)) {
+                return -1;
+            }
+            if (is_array($NewTitle)) {
+                $ID = 0;
+                foreach ($_SESSION[$this->ImageMapIndex] as $Key => $Settings) {
+                    if ($Settings[3] == $OldTitle && isset($NewTitle[$ID])) {
+                        $_SESSION[$this->ImageMapIndex][$Key][3] = $NewTitle[$ID];
+                        $ID++;
+                    }
+                }
+            } else {
+                foreach ($_SESSION[$this->ImageMapIndex] as $Key => $Settings) {
+                    if ($Settings[3] == $OldTitle) {
+                        $_SESSION[$this->ImageMapIndex][$Key][3] = $NewTitle;
+                    }
+                }
+            }
+        } elseif ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_FILE) {
+            $TempArray = [];
+            $Handle = $this->openFileHandle();
+            if ($Handle) {
+                while (($Buffer = fgets($Handle, 4096)) !== false) {
+                    $Fields = preg_split(
+                        sprintf("/%s/", IMAGE_MAP_DELIMITER),
+                        str_replace([chr(10), chr(13)], "", $Buffer)
+                    );
+                    $TempArray[] = [$Fields[0], $Fields[1], $Fields[2], $Fields[3], $Fields[4]];
+                }
+                fclose($Handle);
+
+                if (is_array($NewTitle)) {
+                    $ID = 0;
+                    foreach ($TempArray as $Key => $Settings) {
+                        if ($Settings[3] == $OldTitle && isset($NewTitle[$ID])) {
+                            $TempArray[$Key][3] = $NewTitle[$ID];
+                            $ID++;
+                        }
+                    }
+                } else {
+                    foreach ($TempArray as $Key => $Settings) {
+                        if ($Settings[3] == $OldTitle) {
+                            $TempArray[$Key][3] = $NewTitle;
+                        }
+                    }
+                }
+
+                $Handle = $this->openFileHandle("w");
+                foreach ($TempArray as $Key => $Settings) {
+                    fwrite(
+                        $Handle,
+                        sprintf(
+                            "%s%s%s%s%s%s%s%s%s\r\n",
+                            $Settings[0],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[1],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[2],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[3],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[4]
+                        )
+                    );
+                }
+                fclose($Handle);
+            }
+        }
+    }
+
+    /**
+     * Replace the values of the image map contents
+     * @param string $Title
+     * @param array $Values
+     * @return null|int
+     */
+    public function replaceImageMapValues($Title, array $Values)
+    {
+        if ($this->ImageMapStorageMode == null) {
+            return -1;
+        }
+
+        $Values = $this->removeVOIDFromArray($Title, $Values);
+        $ID = 0;
+        if ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_SESSION) {
+            if (!isset($_SESSION)) {
+                return -1;
+            }
+            foreach ($_SESSION[$this->ImageMapIndex] as $Key => $Settings) {
+                if ($Settings[3] == $Title) {
+                    if (isset($Values[$ID])) {
+                        $_SESSION[$this->ImageMapIndex][$Key][4] = $Values[$ID];
+                    }
+                    $ID++;
+                }
+            }
+        } elseif ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_FILE) {
+            $TempArray = [];
+            $Handle = $this->openFileHandle();
+            if ($Handle) {
+                while (($Buffer = fgets($Handle, 4096)) !== false) {
+                    $Fields = preg_split(
+                        "/" . IMAGE_MAP_DELIMITER . "/",
+                        str_replace([chr(10), chr(13)], "", $Buffer)
+                    );
+                    $TempArray[] = [$Fields[0], $Fields[1], $Fields[2], $Fields[3], $Fields[4]];
+                }
+                fclose($Handle);
+
+                foreach ($TempArray as $Key => $Settings) {
+                    if ($Settings[3] == $Title) {
+                        if (isset($Values[$ID])) {
+                            $TempArray[$Key][4] = $Values[$ID];
+                        }
+                        $ID++;
+                    }
+                }
+
+                $Handle = $this->openFileHandle("w");
+                foreach ($TempArray as $Key => $Settings) {
+                    fwrite(
+                        $Handle,
+                        sprintf(
+                            "%s%s%s%s%s%s%s%s%s\r\n",
+                            $Settings[0],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[1],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[2],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[3],
+                            IMAGE_MAP_DELIMITER,
+                            $Settings[4]
+                        )
+                    );
+                }
+                fclose($Handle);
+            }
+        }
+    }
+
+    /**
+     * Dump the image map
+     * @param string $Name
+     * @param int $StorageMode
+     * @param string $UniqueID
+     * @param string $StorageFolder
+     */
+    public function dumpImageMap(
+        $Name = "pChart",
+        $StorageMode = IMAGE_MAP_STORAGE_SESSION,
+        $UniqueID = "imageMap",
+        $StorageFolder = "tmp"
+    ) {
+        $this->ImageMapIndex = $Name;
+        $this->ImageMapStorageMode = $StorageMode;
+
+        if ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_SESSION) {
+            if (!isset($_SESSION)) {
+                session_start();
+            }
+            if ($_SESSION[$Name] != null) {
+                foreach ($_SESSION[$Name] as $Key => $Params) {
+                    echo $Params[0] . IMAGE_MAP_DELIMITER . $Params[1]
+                    . IMAGE_MAP_DELIMITER . $Params[2] . IMAGE_MAP_DELIMITER
+                    . $Params[3] . IMAGE_MAP_DELIMITER . $Params[4] . "\r\n";
+                }
+            }
+        } elseif ($this->ImageMapStorageMode == IMAGE_MAP_STORAGE_FILE) {
+            if (file_exists($StorageFolder . "/" . $UniqueID . ".map")) {
+                $Handle = @fopen($StorageFolder . "/" . $UniqueID . ".map", "r");
+                if ($Handle) {
+                    while (($Buffer = fgets($Handle, 4096)) !== false) {
+                        echo $Buffer;
+                    }
+                }
+                fclose($Handle);
+
+                if ($this->ImageMapAutoDelete) {
+                    unlink($StorageFolder . "/" . $UniqueID . ".map");
+                }
+            }
+        }
+
+        /* When the image map is returned to the client, the script ends */
+        exit();
+    }
+
+    /**
+     * Return the HTML converted color from the RGB composite values
+     * @param int $R
+     * @param int $G
+     * @param int $B
+     * @return string
+     */
+    public function toHTMLColor($R, $G, $B)
+    {
+        $R = intval($R);
+        $G = intval($G);
+        $B = intval($B);
+        $R = dechex($R < 0 ? 0 : ($R > 255 ? 255 : $R));
+        $G = dechex($G < 0 ? 0 : ($G > 255 ? 255 : $G));
+        $B = dechex($B < 0 ? 0 : ($B > 255 ? 255 : $B));
+        $Color = "#" . (strlen($R) < 2 ? '0' : '') . $R;
+        $Color .= (strlen($G) < 2 ? '0' : '') . $G;
+        $Color .= (strlen($B) < 2 ? '0' : '') . $B;
+
+        return $Color;
+    }
+
+    /**
+     * Reverse an array of points
+     * @param array $Plots
+     * @return array
+     */
+    public function reversePlots(array $Plots)
+    {
+        $Result = [];
+        for ($i = count($Plots) - 2; $i >= 0; $i = $i - 2) {
+            $Result[] = $Plots[$i];
+            $Result[] = $Plots[$i + 1];
+        }
+        return $Result;
+    }
+
+    /**
+     * Mirror Effect
+     * @param int $X
+     * @param int $Y
+     * @param int $Width
+     * @param int $Height
+     * @param array $Format
+     */
+    public function drawAreaMirror($X, $Y, $Width, $Height, array $Format = [])
+    {
+        $StartAlpha = isset($Format["StartAlpha"]) ? $Format["StartAlpha"] : 80;
+        $EndAlpha = isset($Format["EndAlpha"]) ? $Format["EndAlpha"] : 0;
+
+        $AlphaStep = ($StartAlpha - $EndAlpha) / $Height;
+
+        $Picture = imagecreatetruecolor($this->XSize, $this->YSize);
+        imagecopy($Picture, $this->Picture, 0, 0, 0, 0, $this->XSize, $this->YSize);
+
+        for ($i = 1; $i <= $Height; $i++) {
+            if ($Y + ($i - 1) < $this->YSize && $Y - $i > 0) {
+                imagecopymerge(
+                    $Picture,
+                    $this->Picture,
+                    $X,
+                    $Y + ($i - 1),
+                    $X,
+                    $Y - $i,
+                    $Width,
+                    1,
+                    $StartAlpha - $AlphaStep * $i
+                );
+            }
+        }
+
+        imagecopy($this->Picture, $Picture, 0, 0, 0, 0, $this->XSize, $this->YSize);
+    }
+
+    /**
+     * Open a handle to image storage file.
+     * @param string $mode
+     * @return resource
+     */
+    private function openFileHandle($mode = "r")
+    {
+        return @fopen(
+            sprintf("%s/%s.map", $this->ImageMapStorageFolder, $this->ImageMapFileName),
+            $mode
+        );
+    }
+}

+ 0 - 0
vendor/szymach/c-pchart/tests/_data/.gitkeep


二進制
vendor/szymach/c-pchart/tests/_data/accept.png


+ 11 - 0
vendor/szymach/c-pchart/tests/_data/test_palette.txt

@@ -0,0 +1,11 @@
+244, 91,   91, 100
+128, 133, 233, 100
+141,  70,  84, 100
+119, 152, 191, 100
+170, 238, 238, 100
+255,   0, 102, 100
+238, 170, 238, 100
+85,  191,  59, 100
+223,  83,  83, 100
+119, 152, 191, 100
+170, 238, 238, 100

+ 2 - 0
vendor/szymach/c-pchart/tests/_output/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 42 - 0
vendor/szymach/c-pchart/tests/_support/Helper/Unit.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Test\CpChart\Helper;
+
+use Codeception\Module;
+use Codeception\Module\Filesystem;
+
+class Unit extends Module
+{
+    public function _beforeSuite($settings = [])
+    {
+        $chartDir = $this->getChartDirectoryPath();
+        if (!is_dir($chartDir)) {
+            mkdir($chartDir);
+        }
+
+        $this->clearOutputDirectory();
+    }
+
+    public function _afterSuite($settings = [])
+    {
+        $this->clearOutputDirectory();
+    }
+
+    private function clearOutputDirectory()
+    {
+        $this->getFileSystem()->cleanDir($this->getChartDirectoryPath());
+    }
+
+    private function getChartDirectoryPath()
+    {
+        return sprintf(__DIR__."/../../_output/charts");
+    }
+
+    /**
+     * @return Filesystem
+     */
+    private function getFileSystem()
+    {
+        return $this->getModule('Filesystem');
+    }
+}

Some files were not shown because too many files changed in this diff