├── .gitignore ├── examples ├── font │ └── font.ttf ├── line.php ├── badInput.php ├── largeKeys.php ├── html.php ├── stretched.php ├── sinus.php ├── floats.php ├── realData.php ├── image.php ├── randomValues.php └── example.php ├── phpstan.neon ├── src ├── Colorizers │ ├── ColorException.php │ ├── ColorizerInterface.php │ ├── ImageColorizer.php │ ├── HTMLColorizer.php │ └── AsciiColorizer.php ├── Settings.php ├── Chart.php └── Linechart.php ├── LICENCE.md ├── ecs.yml ├── composer.json ├── README.md └── log └── phpstan.html /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | -------------------------------------------------------------------------------- /examples/font/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noximo/PHP-colored-ascii-linechart/HEAD/examples/font/font.ttf -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | autoload_files: 3 | - vendor/autoload.php 4 | excludes_analyse: 5 | - %rootDir%/vendor 6 | ignoreErrors: 7 | checkMissingIterableValueType: false 8 | -------------------------------------------------------------------------------- /src/Colorizers/ColorException.php: -------------------------------------------------------------------------------- 1 | setFPS(40); 11 | 12 | $lineGraph = new Linechart(); 13 | $lineGraph->setSettings($settings); 14 | 15 | try { 16 | $linechart = new Linechart(); 17 | $linechart->addMarkers([1, 1, 1, 1, 1, 1])->chart()->print(); 18 | } catch (\Throwable $e) { 19 | echo $e->getMessage(); 20 | } 21 | -------------------------------------------------------------------------------- /examples/badInput.php: -------------------------------------------------------------------------------- 1 | 2, 13 | 9 => 2, 14 | 'a' => 12, 15 | 2 => 8, 16 | 15 => 4, 17 | 14 => 8, 18 | 8 => 2, 19 | 3 => 2, 20 | 4 => 2, 21 | ]; 22 | $lineGraph->addMarkers($lineA, [AsciiColorizer::GREEN, AsciiColorizer::BOLD], [AsciiColorizer::RED]); 23 | 24 | $lineGraph->chart()->print(); 25 | -------------------------------------------------------------------------------- /src/Colorizers/ImageColorizer.php: -------------------------------------------------------------------------------- 1 | 2, 13 | 14537 => 2, 14 | 14538 => 8, 15 | 14539 => 8, 16 | 14540 => 4, 17 | 14541 => 4, 18 | 14542 => 8, 19 | 14543 => 8, 20 | 14544 => 8, 21 | 14545 => 8, 22 | 14546 => 2, 23 | 14547 => 2, 24 | 14548 => 2, 25 | 14549 => 2, 26 | ]; 27 | $lineGraph->addMarkers($lineA, [AsciiColorizer::GREEN, AsciiColorizer::BOLD]); 28 | 29 | $lineGraph->chart()->print(); 30 | -------------------------------------------------------------------------------- /examples/html.php: -------------------------------------------------------------------------------- 1 | setColorizer(new HTMLColorizer()); 13 | $lineA = []; 14 | 15 | for ($i = 0; $i < +120; $i++) { 16 | $lineA[] = 10 * sin($i * ((M_PI * 4) / 120)); 17 | } 18 | 19 | $lineGraph->addLine(0, ['color:black'], Linechart::FULL_LINE); 20 | $lineGraph->addMarkers($lineA, ['color: green'], ['color: red']); 21 | $lineGraph->setSettings($settings); 22 | 23 | $lineGraph->chart()->print(); 24 | -------------------------------------------------------------------------------- /examples/stretched.php: -------------------------------------------------------------------------------- 1 | setFPS(40); 12 | 13 | $lineGraph = new Linechart(); 14 | $lineGraph->setSettings($settings); 15 | 16 | try { 17 | $line = []; 18 | for ($i = 0; $i < 50; $i++) { 19 | $line[$i] = $i; 20 | } 21 | 22 | $lineGraph->addMarkers($line, [AsciiColorizer::GREEN], [AsciiColorizer::RED]); 23 | 24 | $lineGraph->chart()->clearScreen()->print()->wait(); 25 | $lineGraph->clearAllMarkers(); 26 | } catch (\Throwable $e) { 27 | echo $e->getMessage(); 28 | } 29 | -------------------------------------------------------------------------------- /examples/sinus.php: -------------------------------------------------------------------------------- 1 | setFPS(120); 13 | 14 | for ($y = 0; $y < 1; $y++) { 15 | $lineA = []; 16 | for ($i = $y; $i < $y + 120; $i++) { 17 | $lineA[] = 1 * sin($i * ((M_PI * 4) / 120)); 18 | } 19 | 20 | $lineGraph->addMarkers($lineA, [AsciiColorizer::GREEN, AsciiColorizer::BOLD], [AsciiColorizer::RED]); 21 | 22 | $lineGraph->setSettings($settings); 23 | $lineGraph->chart()->clearScreen()->print()->wait(); 24 | $lineGraph->clearAllMarkers(); 25 | } 26 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tomas Pospisil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Colorizers/HTMLColorizer.php: -------------------------------------------------------------------------------- 1 | %s", $cssStyles, $text); 25 | } 26 | 27 | public function getEOL(): string 28 | { 29 | return '
'; 30 | } 31 | 32 | public function processFinalText(string $text): string 33 | { 34 | $div = "
"; 35 | $text = str_replace([' ', 'span '], [' ', 'span '], $text); 36 | $endDiv = '
'; 37 | 38 | return $div . $text . $endDiv; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/floats.php: -------------------------------------------------------------------------------- 1 | setFPS(40); 12 | $settings->setHeight(30); 13 | $lineGraph = new Linechart(); 14 | $lineGraph->setSettings($settings); 15 | 16 | try { 17 | $line = []; 18 | for ($i = 0; $i < 120; $i++) { 19 | $line[$i] = $lineA[$i - 1] ?? 1 + (lcg_value() * random_int(-1, 1)); 20 | } 21 | for ($y = 0; $y < 1500; $y++) { 22 | array_shift($line); 23 | $line[] = end($line) + (lcg_value() * random_int(-1, 1)); 24 | $lineGraph->addMarkers($line, [AsciiColorizer::GREEN], [AsciiColorizer::RED]); 25 | 26 | //$lineGraph->addLine($line[0] * 1.1, [AsciiColorizer::CYAN], Linechart::FULL_LINE); 27 | 28 | $lineGraph->chart()->clearScreen()->print()->wait(); 29 | $lineGraph->clearAllMarkers(); 30 | } 31 | } catch (\Throwable $e) { 32 | echo $e->getMessage(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/realData.php: -------------------------------------------------------------------------------- 1 | setFPS(40); 12 | $lineGraph = new Linechart(); 13 | $lineGraph->setSettings($settings); 14 | 15 | try { 16 | $line = [0.0208, 0.020858, 0.021, 0.021, 0.0211, 0.0211, 0.0211, 0.0211, 0.0211, 0.021056, 0.0211, 0.0211, 0.0211, 0.0211, 0.0211, 0.0211, 0.0211, 0.0211, 0.021124, 0.0214, 0.0215, 0.021436, 0.02149, 0.021488, 0.02149, 0.02145, 0.02145, 0.021406, 0.02145, 0.02145, 0.02145, 0.0214, 0.02145, 0.021487, 0.02149, 0.021482, 0.02148, 0.02148, 0.0215, 0.0215, 0.0215, 0.0215, 0.021499, 0.021473, 0.021454, 0.021497, 0.021489, 0.021454, 0.021705, 0.02151, 0.021513]; 17 | 18 | $lineGraph->addMarkers($line, [AsciiColorizer::GREEN], [AsciiColorizer::RED]); 19 | 20 | $lineGraph->chart()->clearScreen()->print()->wait(); 21 | $lineGraph->clearAllMarkers(); 22 | } catch (\Throwable $e) { 23 | echo $e->getMessage(); 24 | } 25 | -------------------------------------------------------------------------------- /ecs.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/psr2.yaml' } 3 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/psr12.yaml' } 4 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/common.yaml' } 5 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/clean-code.yaml' } 6 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/php70.yaml' } 7 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/php71.yaml' } 8 | - { resource: '%vendor_dir%/symplify/easy-coding-standard/config/set/symplify.yaml' } 9 | services: 10 | 11 | parameters: 12 | skip: 13 | PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer: ~ 14 | Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer: ~ 15 | Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer: ~ 16 | SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff: ~ 17 | Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff: ~ 18 | Symplify\CodingStandard\Sniffs\CleanCode\ForbiddenStaticFunctionSniff: ~ 19 | PhpCsFixer\Fixer\Import\OrderedImportsFixer: ~ 20 | Symplify\CodingStandard\Fixer\Commenting\BlockPropertyCommentFixer: ~ 21 | Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff: ~ 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noximo/php-colored-ascii-linechart", 3 | "description": "Pretty line graphs in your console, html or images", 4 | "keywords": [ 5 | "chart", 6 | "graph", 7 | "linechart", 8 | "linegraph", 9 | "ascii", 10 | "marker", 11 | "stock" 12 | ], 13 | "type": "library", 14 | "authors": [ 15 | { 16 | "name": "Tomas Pospisil", 17 | "email": "pospisilt@gmail.com" 18 | } 19 | ], 20 | "autoload": { 21 | "psr-4": { 22 | "noximo\\PHPColoredAsciiLinechart\\": "src/" 23 | } 24 | }, 25 | "require": { 26 | "php": ">= 7.1", 27 | "ext-json": ">=1.3.7" 28 | }, 29 | "license": "MIT", 30 | "require-dev": { 31 | "php-parallel-lint/php-parallel-lint": "^v1.0.0", 32 | "phpstan/phpstan": "^0.12", 33 | "phpstan/phpstan-deprecation-rules": "^0.12", 34 | "phpstan/phpstan-strict-rules": "^0.12", 35 | "roave/security-advisories": "dev-master", 36 | "symplify/easy-coding-standard": "^6.0" 37 | }, 38 | "scripts": { 39 | "check-cs": "ecs check src examples", 40 | "fix-cs": "ecs check src examples --fix", 41 | "lint": "parallel-lint --colors --exclude vendor .", 42 | "phpstan": "phpstan analyze src examples --level max -c phpstan.neon", 43 | "test": [ 44 | "@lint", 45 | "@check-cs", 46 | "@phpstan" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/image.php: -------------------------------------------------------------------------------- 1 | setColorizer(new ImageColorizer()); 14 | $lineA = []; 15 | 16 | for ($i = 0; $i < +120; $i++) { 17 | $lineA[] = 10 * sin($i * ((M_PI * 4) / 120)); 18 | } 19 | 20 | $lineGraph->addMarkers($lineA, [AsciiColorizer::WHITE]); 21 | 22 | $lineGraph->setSettings($settings); 23 | $graph = $lineGraph->chart(); 24 | // Set http headers 25 | header('Content-Type: image/png'); 26 | 27 | $font = __DIR__ . '/font/font.ttf'; 28 | $fontSize = 40; 29 | 30 | $text = (string) $graph; 31 | 32 | // Calculate the required width to hold this text 33 | $enclosingBox = imagettfbbox($fontSize, 0, $font, $text); 34 | $width = $enclosingBox[2] - $enclosingBox[0] - 10; 35 | 36 | $height = $fontSize * $graph->getSettings()->getHeight() * 2; 37 | 38 | // Create the image and define colours 39 | $im = imagecreatetruecolor($width + $fontSize, $height + $fontSize); 40 | $white = imagecolorallocate($im, 255, 255, 255); 41 | $grey = imagecolorallocate($im, 0, 0, 0); 42 | 43 | imagefilledrectangle($im, 0, 0, $width - 1, $height - 1, $grey); 44 | 45 | imagettftext($im, $fontSize, 0, $fontSize, $fontSize, $white, $font, $text); 46 | 47 | // Output and cleanup 48 | imagepng($im); 49 | imagedestroy($im); 50 | -------------------------------------------------------------------------------- /examples/randomValues.php: -------------------------------------------------------------------------------- 1 | setHeight(30); 12 | $settings->setFPS(120); 13 | $lineGraph = new Linechart(); 14 | $lineGraph->setSettings($settings); 15 | 16 | try { 17 | $lineA = []; 18 | $lineB = []; 19 | $lineC = []; 20 | $lineD = []; 21 | for ($i = 0; $i < 120; $i++) { 22 | $lineA[$i] = $lineA[$i - 1] ?? 1 + random_int(-2, 2); 23 | $lineB[$i] = $lineB[$i - 1] ?? 1 + random_int(-2, 2); 24 | $lineC[$i] = $lineC[$i - 1] ?? 1 + random_int(-2, 2); 25 | $lineD[$i] = $lineD[$i - 1] ?? 1 + random_int(-2, 2); 26 | } 27 | for ($y = 0; $y < 1500; $y++) { 28 | array_shift($lineA); 29 | $lineA[] = end($lineA) + random_int(-2, 2); 30 | array_shift($lineB); 31 | $lineB[] = end($lineB) + random_int(-2, 2); 32 | array_shift($lineC); 33 | $lineC[] = end($lineC) + random_int(-2, 2); 34 | array_shift($lineD); 35 | $lineD[] = end($lineD) + random_int(-2, 2); 36 | 37 | $lineGraph->addMarkers($lineA, [AsciiColorizer::GREEN, AsciiColorizer::BOLD]); 38 | $lineGraph->addMarkers($lineB, [AsciiColorizer::GREEN], [AsciiColorizer::RED]); 39 | $lineGraph->addMarkers($lineC, [AsciiColorizer::CYAN, AsciiColorizer::BOLD]); 40 | $lineGraph->addMarkers($lineD, [AsciiColorizer::WHITE, AsciiColorizer::BOLD]); 41 | 42 | $lineGraph->addLine(0, [AsciiColorizer::CYAN], Linechart::FULL_LINE); 43 | 44 | $lineGraph->chart()->clearScreen()->print()->wait(); 45 | $lineGraph->clearAllMarkers(); 46 | } 47 | } catch (Throwable $throwable) { 48 | echo $throwable->getMessage(); 49 | } 50 | -------------------------------------------------------------------------------- /examples/example.php: -------------------------------------------------------------------------------- 1 | setColorizer(new AsciiColorizer())//Colorizer, choose between Ascii, HTML and image colorizers 15 | ->setFPS(24)//control speed of Graph::wait method 16 | ->setHeight(30)//Set fixed height of graph. Graph will scale accordingly 17 | ->setPadding(5, ' ')//Set lenght of a padding and character used 18 | ->setOffset(10)//Offset left border 19 | ->setFormat( //control how y axis labels will be printed out 20 | function ($x, Settings $settings) { 21 | $padding = $settings->getPadding(); 22 | $paddingLength = strlen($padding); 23 | 24 | return substr($padding . round($x, 2), -$paddingLength); 25 | } 26 | ); 27 | 28 | $lineGraph = new Linechart(); 29 | $lineGraph->setSettings($settings); 30 | 31 | for ($y = 0; $y < 40000; $y++) { //Move sinusoid 32 | $lineA = []; 33 | $lineB = []; 34 | for ($i = $y; $i < $y + 120; $i++) { 35 | $lineA[] = 10 * sin($i * ((M_PI * 4) / 120)); //Draw sinusoid 36 | $lineB[] = 20 * sin($i * ((M_PI * 4) / 120)); //Draw sinusoid 37 | } 38 | 39 | $lineGraph->addMarkers( 40 | $lineA, //graph data - note that any elements with non-integer keys will be discarded 41 | [AsciiColorizer::GREEN, AsciiColorizer::BOLD], // Default color of line. Can be ommited. You can combine mutliple color codes together. If you set up HTML colorizer, you can enter css styles instead of codes. See below 42 | [AsciiColorizer::RED, AsciiColorizer::BOLD] // Color of downtrend. Can be ommited, then default color will be used instead. 43 | ); 44 | //Pro-tip - combine color with bold style - it will pop-out nicely 45 | 46 | $lineGraph->addMarkers($lineB, [AsciiColorizer::CYAN]); //Add as many datasets as you want 47 | 48 | $lineGraph->addLine( //Add a guiding line - a zero line for example 49 | 0, //Alias y coordinate 50 | [AsciiColorizer::CYAN], //You can set color the same way as with series 51 | Linechart::FULL_LINE //Choose between full line and 52 | ); 53 | 54 | $lineGraph->addPoint( 55 | 10, 56 | 15, 57 | [AsciiColorizer::LIGHT_BLUE], 58 | Linechart::CROSS //Point can be made more visible with crosslines. Default is Linegraph::POINT 59 | ); 60 | 61 | $graph = $lineGraph->chart(); //Graph is an object with all data drawn. It can be simply echoed or we can leverage its methods for output control 62 | 63 | $graph->clearScreen(); //clears already outputed graphs 64 | $graph->print(); //Alias of echo $graph with fluent method call 65 | $graph->wait(); //Naive implementation of animation. It simply sleeps for n microseconds (defined by setFPS earlier). It does not take into account time spent on graph generation or on retrieving data 66 | $lineGraph->clearAllMarkers(); //Get rid of already processed graph data so they won't get printed again. 67 | } 68 | -------------------------------------------------------------------------------- /src/Settings.php: -------------------------------------------------------------------------------- 1 | format = function ($x, self $settings) { 57 | $padding = $settings->getPadding(); 58 | $decimals = $settings->getDecimals(); 59 | $paddingLength = \strlen($padding); 60 | 61 | return substr($padding . round($x, $decimals), -$paddingLength); 62 | }; 63 | } 64 | 65 | public function getPadding(): string 66 | { 67 | return $this->padding; 68 | } 69 | 70 | public function setPadding(int $length, ?string $char = null): self 71 | { 72 | if ($char === null || $char === '') { 73 | $padding = ' '; 74 | } else { 75 | $padding = $char; 76 | } 77 | $this->padding = str_pad('', $length, $padding); 78 | 79 | return $this; 80 | } 81 | 82 | public function getDecimals(): int 83 | { 84 | return $this->decimals; 85 | } 86 | 87 | public function setDecimals(int $decimals): self 88 | { 89 | $this->decimals = $decimals; 90 | 91 | return $this; 92 | } 93 | 94 | public function getHeight(): ?int 95 | { 96 | return $this->height; 97 | } 98 | 99 | public function setHeight(int $height): self 100 | { 101 | $this->height = $height; 102 | 103 | return $this; 104 | } 105 | 106 | public function getOffset(): int 107 | { 108 | return $this->offset; 109 | } 110 | 111 | public function setOffset(int $offset): self 112 | { 113 | $this->offset = $offset; 114 | 115 | return $this; 116 | } 117 | 118 | public function getFormat(): callable 119 | { 120 | return $this->format; 121 | } 122 | 123 | /** 124 | * @param callable $format =function ($x, Settings $config) { 125 | * $padding = $config->getPadding(); 126 | * $paddingLength = strlen($padding); 127 | * return substr($padding . round($x, 2), -$paddingLength); 128 | * } 129 | * @return Settings 130 | */ 131 | public function setFormat(callable $format): self 132 | { 133 | $this->format = $format; 134 | 135 | return $this; 136 | } 137 | 138 | public function getFps(): int 139 | { 140 | return $this->fps; 141 | } 142 | 143 | public function setFPS(int $fps): self 144 | { 145 | $this->fps = $fps; 146 | 147 | return $this; 148 | } 149 | 150 | public function getColorizer(): ColorizerInterface 151 | { 152 | return $this->colorizer ?? new AsciiColorizer(); 153 | } 154 | 155 | public function setColorizer(ColorizerInterface $colorizer): self 156 | { 157 | $this->colorizer = $colorizer; 158 | 159 | return $this; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Colorizers/AsciiColorizer.php: -------------------------------------------------------------------------------- 1 | colorExists((int) $color)) { 156 | throw new ColorException('Unknown color ' . $color . ', use constans of class Color'); 157 | } 158 | } 159 | 160 | $chr27 = chr(27); 161 | 162 | return sprintf('%s[0m%s[%sm%s%s[0m', $chr27, $chr27, implode(';', $colors), $text, $chr27); 163 | } 164 | 165 | /** 166 | * @throws ReflectionException 167 | */ 168 | public function colorExists(int $color): bool 169 | { 170 | if (count(self::$constants) === 0) { 171 | $oClass = new ReflectionClass(self::class); 172 | self::$constants = $oClass->getConstants(); 173 | } 174 | 175 | return in_array($color, self::$constants, true); 176 | } 177 | 178 | public function getEOL(): string 179 | { 180 | return PHP_EOL; 181 | } 182 | 183 | public function processFinalText(string $text): string 184 | { 185 | return $text; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-colored-ascii-linechart 2 | 3 | [![Sinus output](https://i.imgur.com/Wc7OjvO.gif)](https://i.imgur.com/Wc7OjvO.gif) 4 | 5 | Create beautiful, versatile ASCII line-charts within Terminal, written in `PHP`. 6 | 7 | - Create multiple lines in a single chart, each with its own color, 8 | - Use points in your chart , 9 | - Scale the chart to a desired height or let it grow and shrink freely, 10 | - Have multi-colored lines based on uptrends and downtrends, 11 | - Print charts as ASCII colored text, a HTML snippet or png image, 12 | - Use simple API that helps with animating sequence of charts. 13 | 14 | _Built upon [kroitor/asciichart](https://github.com/kroitor/asciichart)_ 15 | 16 | ## Installation 17 | ``` 18 | $ composer require noximo/php-colored-ascii-linechart 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### Simple Output: 24 | 25 | ```php 26 | $linechart = new Linechart(); 27 | echo $linechart->addMarkers([1,2,3,4,5,6])->addPoint(4, 2)->chart(); 28 | ``` 29 | This will print a simple chart with a single point in default colors. 30 | 31 | ### Advanced Output: 32 | 33 | ```php 34 | $settings = new Settings(); 35 | // Note that any setting can be ommited. 36 | $settings 37 | ->setColorizer(new AsciiColorizer()) // Colorizer, choose between Ascii, HTML and image colorizers 38 | ->setFPS(24) // Control speed of chart::wait method 39 | ->setHeight(30) // Set fixed height of chart. chart will scale accordingly. If not set, height will be calculated based on highest and lowest numbers across all sets of markers. 40 | ->setPadding(5, ' ') // Set lenght of a padding and character used 41 | ->setOffset(10) // Offset left border 42 | ->setFormat( // Control how y axis labels will be printed out 43 | function ($x, Settings $settings) { 44 | $padding = $settings->getPadding(); 45 | $paddingLength = \strlen($padding); 46 | 47 | return substr($padding . round($x, 2), -$paddingLength); 48 | } 49 | ); 50 | 51 | $linechart = new Linechart(); 52 | $linechart->setSettings($settings); 53 | 54 | for ($y = 0; $y < 1200; $y++) { // Move sinusoid 55 | $lineA = []; 56 | $lineB = []; 57 | for ($i = $y; $i < $y + 120; $i++) { 58 | $lineA[] = 10 * sin($i * ((M_PI * 4) / 120)); // Draw sinusoid 59 | $lineB[] = 20 * sin($i * ((M_PI * 4) / 120)); // Draw sinusoid 60 | } 61 | 62 | $linechart->addMarkers( 63 | $lineA, // Chart data - note that any elements with non-integer keys will be discarded 64 | [AsciiColorizer::GREEN, AsciiColorizer::BOLD], // Default color of line. Can be ommited. You can combine mutliple color codes together. If you set up HTML colorizer, you can enter css styles instead of codes. See below 65 | [AsciiColorizer::RED, AsciiColorizer::BOLD] // Color of downtrend. Can be ommited, then default color will be used instead. 66 | ); // Pro-tip - combine color with bold style - it will pop-out nicely 67 | 68 | $linechart->addMarkers($lineB, [AsciiColorizer::CYAN]); // Add as many datasets as you want 69 | 70 | $linechart->addLine( // Add a guiding line - a zero line for example 71 | 0, // Alias y coordinate 72 | [AsciiColorizer::CYAN], // You can set color the same way as with markers 73 | Linechart::FULL_LINE // Choose between full line and dashed line 74 | ); 75 | 76 | $linechart->addPoint( 77 | 10, 78 | 15, 79 | [AsciiColorizer::LIGHT_BLUE], 80 | Linechart::CROSS // Point can be made more visible with crosslines. Default is Linechart::POINT 81 | ); 82 | 83 | 84 | $chart = $linechart->chart(); // Chart is an object with all data drawn. It can be simply echoed or we can leverage its methods for output control 85 | 86 | $chart->clearScreen(); // Clears already outputed charts 87 | $chart->print(); // Alias of echo $chart with fluent method call 88 | $chart->wait(); // Naive implementation of animation. It simply sleeps for n microseconds (defined by setFPS earlier). It does not take into account time spent on chart generation or on retrieving data 89 | $linechart->clearAllMarkers(); // Get rid of already processed chart data so they won't get printed again. 90 | } 91 | ``` 92 | 93 | This will print out the [chart](https://i.imgur.com/Wc7OjvO.gif) shown in the [heading](https://github.com/noximo/PHP-colored-ascii-linechart#php-colored-ascii-linechart). 94 | 95 | ### HTML Output 96 | 97 | ```php 98 | $linechart = new Linechart(); 99 | $settings = new Settings(); // Settings are needed in this case 100 | $settings->setColorizer(new HTMLColorizer()); // Here you need to set up HTMLColorizer 101 | 102 | $lineA = []; 103 | for ($i = 0; $i < +120; $i++) { 104 | $lineA[] = 10 * sin($i * ((M_PI * 4) / 120)); 105 | } 106 | 107 | $linechart->addLine(0, ['color:white'], Linechart::FULL_LINE); // Use css styles instead of ascii color codes 108 | $linechart->addMarkers($lineA, ['color: green'], ['color: red']); 109 | $linechart->setSettings($settings); 110 | 111 | echo $linechart->chart(); 112 | ``` 113 | 114 | [![Sinus output](https://i.imgur.com/Qw78k9k.png)](https://i.imgur.com/Qw78k9k.png) 115 | 116 | ### Image Output 117 | Images are a work in progress. You can look in `/examples` folder for a *beta* implementation. 118 | 119 | ## Development Pathway / To-Do 120 | 121 | - Refactoring of colorizers and printers: 122 | - single colorizer regardles of output type, 123 | - chart class rewrite so $chart->toHtml(), $chart->toPng(), $chart->toAscii() etc. exists, 124 | - Proper image support. Animated images through gifs, 125 | - Better customization (backgrounds, borders), 126 | - X axis with labels. 127 | -------------------------------------------------------------------------------- /src/Chart.php: -------------------------------------------------------------------------------- 1 | chart = $chart; 60 | } 61 | 62 | public function __toString() 63 | { 64 | return $this->prepareChart() . $this->prepareText(); 65 | } 66 | 67 | public function getSettings(): Settings 68 | { 69 | return $this->settings; 70 | } 71 | 72 | public function setSettings(Settings $settings): self 73 | { 74 | $this->settings = $settings; 75 | 76 | return $this; 77 | } 78 | 79 | public function getResults(): array 80 | { 81 | return $this->results; 82 | } 83 | 84 | public function getMin(): float 85 | { 86 | return $this->min; 87 | } 88 | 89 | public function setMin(float $min): self 90 | { 91 | $this->min = $min; 92 | 93 | return $this; 94 | } 95 | 96 | public function getMax(): float 97 | { 98 | return $this->max; 99 | } 100 | 101 | public function setMax(float $max): self 102 | { 103 | $this->max = $max; 104 | 105 | return $this; 106 | } 107 | 108 | public function setWidth(int $width): self 109 | { 110 | $this->width = $width; 111 | 112 | return $this; 113 | } 114 | 115 | public function addResult(array $result): void 116 | { 117 | $this->results[] = $result; 118 | } 119 | 120 | public function printAndwait(): self 121 | { 122 | $this->print()->wait(); 123 | 124 | return $this; 125 | } 126 | 127 | public function wait(): self 128 | { 129 | usleep((int) round(1000000 / $this->settings->getFps())); 130 | 131 | return $this; 132 | } 133 | 134 | public function print(): self 135 | { 136 | $this->output($this->prepareChart()); 137 | $this->output($this->prepareText()); 138 | 139 | return $this; 140 | } 141 | 142 | public function toArray(): array 143 | { 144 | $return = []; 145 | foreach ($this->merge() as $row) { 146 | foreach ($row as $cell) { 147 | $return[] = $cell; 148 | } 149 | } 150 | 151 | return $return; 152 | } 153 | 154 | public function clearScreen(bool $useAlternativeMethod = false): self 155 | { 156 | if ($useAlternativeMethod) { 157 | $chr27 = chr(27); 158 | $chr91 = chr(91); 159 | $output = sprintf('%s%sH%s%sJ', $chr27, $chr91, $chr27, $chr91); 160 | } else { 161 | $output = "\033[0;0f"; 162 | } 163 | 164 | $this->output($output); 165 | 166 | return $this; 167 | } 168 | 169 | public function setAlltimeMaxHeight(int $allTimeMaxHeight): self 170 | { 171 | $this->allTimeMaxHeight = $allTimeMaxHeight; 172 | 173 | return $this; 174 | } 175 | 176 | public function printText(): self 177 | { 178 | $text = $this->prepareText(); 179 | 180 | $this->output($text); 181 | 182 | return $this; 183 | } 184 | 185 | private function prepareChart(): string 186 | { 187 | $return = ''; 188 | foreach ($this->merge() as $row) { 189 | foreach ($row as $cell) { 190 | $return .= $cell; 191 | } 192 | $return .= $this->settings->getColorizer()->getEOL(); 193 | } 194 | 195 | return $this->settings->getColorizer()->processFinalText($return); 196 | } 197 | 198 | private function prepareText(): string 199 | { 200 | $return = ''; 201 | foreach ($this->chart->getText() as $row) { 202 | $line = $this->settings->getColorizer()->colorize($row[Linechart::VALUE], $row[Linechart::COLORS]); 203 | $lineLength = strlen($line); 204 | $this->longestText = $lineLength > $this->longestText ? $lineLength : $this->longestText; 205 | $line = str_pad($line, $this->longestText, ' '); 206 | $return .= $line . $this->settings->getColorizer()->getEOL(); 207 | } 208 | 209 | $return = $this->settings->getColorizer()->processFinalText($return); 210 | 211 | $this->chart->clearText(); 212 | 213 | return $return; 214 | } 215 | 216 | private function output(string $output): void 217 | { 218 | $fopen = fopen('php://stdout', 'wb'); 219 | if (is_resource($fopen)) { 220 | fwrite($fopen, $output); 221 | } 222 | } 223 | 224 | private function merge(): array 225 | { 226 | $merged = []; 227 | foreach ($this->results as $result) { 228 | foreach ($result as $x => $row) { 229 | $merged = $this->mergeRow($merged, $row, $x); 230 | } 231 | } 232 | 233 | return $this->adjustAllTimeMaxHeight($merged, $x ?? 0); 234 | } 235 | 236 | private function mergeRow(array $merged, array $row, int $x): array 237 | { 238 | foreach ($row as $y => $cell) { 239 | $cell = (string) $cell; 240 | if ($this->shouldBeMerged($merged, $x, $y, $cell)) { 241 | $merged[$x][$y] = $cell; 242 | } 243 | } 244 | 245 | return $merged; 246 | } 247 | 248 | private function adjustAllTimeMaxHeight(array $merged, int $x): array 249 | { 250 | if ($x < $this->allTimeMaxHeight) { 251 | $width = $this->width + strlen($this->settings->getPadding()); 252 | $filledArray = array_fill(0, $width, ' '); 253 | for ($i = 0, $iMax = $this->allTimeMaxHeight - $x; $i < $iMax; $i++) { 254 | $merged[] = $filledArray; 255 | } 256 | } 257 | 258 | return $merged; 259 | } 260 | 261 | private function shouldBeMerged(array $merged, int $x, int $y, string $cell): bool 262 | { 263 | return !isset($merged[$x][$y]) || ($cell !== ' '); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /log/phpstan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 69 | PHPStan analysis result 70 | 71 |

PHPStan analysis result

72 |
2019-08-20 22:11:06
73 | 74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 91 | 95 | 96 | 97 | 100 | 104 | 105 | 106 | 109 | 113 | 114 | 115 | 118 | 122 | 123 | 124 | 127 | 131 | 132 | 133 | 136 | 140 | 141 | 142 | 145 | 149 | 150 | 151 | 154 | 158 | 159 | 160 | 163 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 177 | 181 | 182 | 183 | 186 | 190 | 191 | 192 | 195 | 199 | 200 | 201 | 204 | 208 | 209 | 210 |
Files with errors: 2
C:\wamp64\www\PHPColoredAsciiLinechart\examples\image.php (9×)
89 | 49 90 | 92 | Parameter #1 $im of function imagedestroy expects resource, resource|false given.
93 | - '#Parameter \#1 \$im of function imagedestroy expects resource, resource\|false given#' 94 |
98 | 48 99 | 101 | Parameter #1 $im of function imagepng expects resource, resource|false given.
102 | - '#Parameter \#1 \$im of function imagepng expects resource, resource\|false given#' 103 |
107 | 45 108 | 110 | Parameter #1 $im of function imagettftext expects resource, resource|false given.
111 | - '#Parameter \#1 \$im of function imagettftext expects resource, resource\|false given#' 112 |
116 | 43 117 | 119 | Parameter #1 $im of function imagefilledrectangle expects resource, resource|false given.
120 | - '#Parameter \#1 \$im of function imagefilledrectangle expects resource, resource\|false given#' 121 |
125 | 43 126 | 128 | Parameter #4 $x2 of function imagefilledrectangle expects int, float|int given.
129 | - '#Parameter \#4 \$x2 of function imagefilledrectangle expects int, float\|int given#' 130 |
134 | 41 135 | 137 | Parameter #1 $im of function imagecolorallocate expects resource, resource|false given.
138 | - '#Parameter \#1 \$im of function imagecolorallocate expects resource, resource\|false given#' 139 |
143 | 40 144 | 146 | Parameter #1 $im of function imagecolorallocate expects resource, resource|false given.
147 | - '#Parameter \#1 \$im of function imagecolorallocate expects resource, resource\|false given#' 148 |
152 | 39 153 | 155 | Parameter #1 $x_size of function imagecreatetruecolor expects int, float|int given.
156 | - '#Parameter \#1 \$x_size of function imagecreatetruecolor expects int, float\|int given#' 157 |
161 | 36 162 | 164 | Only numeric types are allowed in *, int|null given on the right side.
165 | - '#Only numeric types are allowed in \*, int\|null given on the right side#' 166 |
C:\wamp64\www\PHPColoredAsciiLinechart\examples\randomValues.php (4×)
175 | 35 176 | 178 | Only numeric types are allowed in +, int|false given on the left side.
179 | - '#Only numeric types are allowed in \+, int\|false given on the left side#' 180 |
184 | 33 185 | 187 | Only numeric types are allowed in +, int|false given on the left side.
188 | - '#Only numeric types are allowed in \+, int\|false given on the left side#' 189 |
193 | 31 194 | 196 | Only numeric types are allowed in +, int|false given on the left side.
197 | - '#Only numeric types are allowed in \+, int\|false given on the left side#' 198 |
202 | 29 203 | 205 | Only numeric types are allowed in +, int|false given on the left side.
206 | - '#Only numeric types are allowed in \+, int\|false given on the left side#' 207 |
211 |
212 | This file was made thanks to 213 | PHPStan and was outputted by 214 | PHPStan FileOutput 215 |
216 |
217 | 218 | -------------------------------------------------------------------------------- /src/Linechart.php: -------------------------------------------------------------------------------- 1 | [1,2,3.45], SELF::COLORS => [1,2,3]]]; 53 | * ] 54 | */ 55 | private $allmarkers = []; 56 | 57 | /** @var int */ 58 | private $width; 59 | 60 | /** 61 | * @var array|null 62 | */ 63 | private $currentColors; 64 | 65 | /** 66 | * @var float 67 | */ 68 | private $range = 0.0; 69 | 70 | /** 71 | * @var float 72 | */ 73 | private $ratio = 0.0; 74 | 75 | /** 76 | * @var float 77 | */ 78 | private $min2 = 0.0; 79 | 80 | /** 81 | * @var float 82 | */ 83 | private $max2 = 0.0; 84 | 85 | /** 86 | * @var int 87 | */ 88 | private $rows = 1; 89 | 90 | /** 91 | * @var int 92 | */ 93 | private $offset = 0; 94 | 95 | /** 96 | * @var float|null 97 | */ 98 | private $adjuster; 99 | 100 | /** 101 | * @var array 102 | */ 103 | private $text = []; 104 | 105 | /** 106 | * @var Settings|null 107 | */ 108 | private $settings; 109 | 110 | /** 111 | * @var ColorizerInterface 112 | */ 113 | private $colorizer; 114 | 115 | /** 116 | * @param int $x alias x coordinate 117 | * @param float $y alias y coordinate 118 | * @param array|null $colors 119 | * @param string|null $appearance 120 | * @return Linechart 121 | */ 122 | public function addPoint(int $x, float $y, ?array $colors = null, ?string $appearance = null): self 123 | { 124 | $markers = []; 125 | $markers[0] = $y; 126 | $markers[$x] = $y; 127 | if (!in_array($appearance, [self::CROSS, self::POINT], true)) { 128 | $appearance = self::POINT; 129 | } 130 | $this->addMarkerData($markers, $colors, null, $appearance); 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * @param array $markers 137 | * @param array|null $colors 138 | * @param array|null $colorsDown 139 | * @return Linechart 140 | */ 141 | public function addMarkers(array $markers, ?array $colors = null, ?array $colorsDown = null): self 142 | { 143 | $this->addMarkerData($markers, $colors, $colorsDown); 144 | 145 | return $this; 146 | } 147 | 148 | public function chart(): Chart 149 | { 150 | $graph = new Chart($this); 151 | $graph->setSettings($this->getSettings()); 152 | $this->prepareData(); 153 | 154 | foreach ($this->allmarkers as $markersData) { 155 | $result = $this->getResultFromMarkersData($markersData); 156 | 157 | $this->currentColors = null; 158 | $graph->addResult($result); 159 | } 160 | 161 | $graph->setWidth($this->width); 162 | 163 | return $graph; 164 | } 165 | 166 | public function getSettings(): Settings 167 | { 168 | if ($this->settings === null) { 169 | $this->settings = new Settings(); 170 | } 171 | 172 | return $this->settings; 173 | } 174 | 175 | public function setSettings(Settings $settings): self 176 | { 177 | $this->settings = $settings; 178 | 179 | return $this; 180 | } 181 | 182 | public function clearAllMarkers(): self 183 | { 184 | $this->allmarkers = []; 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * @param string $value 191 | * @param string[] $color 192 | */ 193 | public function addText(string $value, array $color): void 194 | { 195 | $this->text[] = [ 196 | self::VALUE => $value, 197 | self::COLORS => $color, 198 | ]; 199 | } 200 | 201 | /** 202 | * @param array $values 203 | * @param float $mainValue 204 | * @param array $colors 205 | */ 206 | public function addSpread(array $values, float $mainValue, array $colors): void 207 | { 208 | foreach ($values as $value) { 209 | $colors = $colors ?? []; 210 | $appearance = $value === 1 ? self::FULL_LINE : self::DASHED_LINE; 211 | $this->addLine($value * $mainValue, $colors ?? [], $appearance); 212 | } 213 | } 214 | 215 | /** 216 | * @param float $value alias y coordinate 217 | * @param array|null $colors 218 | * @param string|null $appearance 219 | * @return Linechart 220 | */ 221 | public function addLine(float $value, ?array $colors = null, ?string $appearance = null): self 222 | { 223 | $markers = []; 224 | $markers[0] = $value; 225 | if (!in_array($appearance, [self::DASHED_LINE, self::FULL_LINE], true)) { 226 | $appearance = self::DASHED_LINE; 227 | } 228 | $this->addMarkerData($markers, $colors, null, $appearance); 229 | 230 | return $this; 231 | } 232 | 233 | public function getText(): array 234 | { 235 | return $this->text; 236 | } 237 | 238 | public function clearText(): void 239 | { 240 | $this->text = []; 241 | } 242 | 243 | /** 244 | * @param array $markers 245 | * @param array|null $colors 246 | * @param array|null $colorsDown 247 | * @param string|null $point 248 | * @return Linechart 249 | */ 250 | private function addMarkerData(array $markers, ?array $colors = null, ?array $colorsDown = null, ?string $point = null): self 251 | { 252 | $markersData = [ 253 | self::MARKERS => $this->normalizeData($markers), 254 | self::COLORS => $colors ?? [], 255 | self::COLORS_DOWN => $colorsDown ?? $colors ?? [], 256 | self::POINT => $point, 257 | ]; 258 | 259 | $this->allmarkers[] = $markersData; 260 | 261 | return $this; 262 | } 263 | 264 | private function prepareData(): void 265 | { 266 | [$min, $max, $width] = $this->findMinMax($this->allmarkers); 267 | 268 | $this->adjuster = $this->findAdjuster($min, $max); 269 | $max = $this->adjust($max); 270 | $min = $this->adjust($min); 271 | 272 | $this->range = max(1, abs($max - $min)); 273 | 274 | $height = max(1, (int) ($this->getSettings()->getHeight() ?? $this->range)); 275 | $this->ratio = $height / $this->range; 276 | 277 | $this->min2 = $min * $this->ratio; 278 | $this->max2 = $max * $this->ratio; 279 | 280 | $this->rows = (int) max(0, abs(round($this->max2 - $this->min2))); 281 | 282 | $this->offset = $this->getSettings()->getOffset(); 283 | 284 | $this->width = $width + $this->offset; 285 | } 286 | 287 | private function getResultFromMarkersData(array $markersData): array 288 | { 289 | $markersData[self::MARKERS] = $this->adjustMarkerValues($markersData[self::MARKERS]); 290 | $this->currentColors = $this->currentColors ?? $markersData[self::COLORS]; 291 | 292 | $result = $this->processBorder($this->prepareResult(), $markersData); 293 | 294 | $isPoint = in_array($markersData[self::POINT], [self::CROSS, self::POINT], true); 295 | $isLine = in_array($markersData[self::POINT], [self::DASHED_LINE, self::FULL_LINE], true); 296 | 297 | foreach ($markersData[self::MARKERS] as $x => $value) { 298 | $y0 = (int) (round($value * $this->ratio) - $this->min2); 299 | 300 | if ($this->isPresent($markersData[self::MARKERS], $x + 1)) { 301 | $result = $this->processLinearGraph($result, $markersData, $x, $y0); 302 | } elseif ($x !== 0 && $isPoint) { 303 | $result = $this->processPoint($result, $markersData, $y0, $x); 304 | } elseif ($x === 0 && $isLine) { 305 | $result = $this->processLine($result, $y0, $markersData[self::POINT]); 306 | } 307 | } 308 | 309 | return $result; 310 | } 311 | 312 | private function normalizeData(array $markers): array 313 | { 314 | $markers = array_filter($markers, '\is_int', ARRAY_FILTER_USE_KEY); 315 | ksort($markers); 316 | 317 | reset($markers); 318 | $firstKey = key($markers); 319 | 320 | $keys = []; 321 | foreach (array_keys($markers) as $key) { 322 | $keys[] = $key - $firstKey; 323 | } 324 | 325 | $combined = array_combine($keys, $markers); 326 | 327 | if ($combined === false) { 328 | return []; 329 | } 330 | 331 | return $combined; 332 | } 333 | 334 | private function findMinMax(array $allmarkers): array 335 | { 336 | $width = 0; 337 | $min = PHP_INT_MAX; 338 | $max = -PHP_INT_MAX; 339 | foreach ($allmarkers as $markers) { 340 | end($markers[self::MARKERS]); 341 | $width = (int) max($width, key($markers[self::MARKERS])); 342 | 343 | /** @var int[][] $markers */ 344 | foreach ($markers[self::MARKERS] as $value) { 345 | if ($value !== null && $value !== false) { 346 | $min = min($min, $value); 347 | $max = max($max, $value); 348 | } 349 | } 350 | } 351 | 352 | return [$min, $max, $width]; 353 | } 354 | 355 | private function findAdjuster(float $min, float $max): ?float 356 | { 357 | $adjuster = null; 358 | $realMin = $max - $min; 359 | 360 | if ($realMin < 1 && $realMin > 0) { 361 | $adjuster = 1 / $realMin; 362 | } 363 | 364 | return $adjuster; 365 | } 366 | 367 | private function adjust(float $number): float 368 | { 369 | if ($this->adjuster !== null) { 370 | $number *= $this->adjuster; 371 | } 372 | 373 | return $number; 374 | } 375 | 376 | private function adjustMarkerValues(array $markers): array 377 | { 378 | if ($this->adjuster === null) { 379 | return $markers; 380 | } 381 | 382 | return array_map(function ($value) { 383 | return $this->adjuster !== null ? $value * $this->adjuster : $value; 384 | }, $markers); 385 | } 386 | 387 | private function processBorder(array $result, array $markersData): array 388 | { 389 | $format = $this->getSettings()->getFormat(); 390 | $y0 = (int) (round($markersData[self::MARKERS][0] * $this->ratio) - $this->min2); 391 | $y = (int) floor($this->min2); 392 | $yMax = (int) ceil($this->max2); 393 | 394 | for (; $y <= $yMax; ++$y) { 395 | $rows = $this->rows === 0 ? 1 : $this->rows; 396 | $rawLabel = $this->max2 / $this->ratio - ($y - $this->min2) * $this->range / $rows; 397 | $rawLabel = $this->deadjust($rawLabel); 398 | $label = $format($rawLabel, $this->getSettings()); 399 | 400 | $border = '┤'; 401 | if ($y - $this->min2 === (float) ($rows - $y0)) { 402 | $label = $this->colorize($label, $this->currentColors); 403 | $border = $this->colorize('┼', $this->currentColors); 404 | } 405 | 406 | $result[$y - $this->min2][max($this->offset - strlen($label), 0)] = $label; 407 | $result[$y - $this->min2][$this->offset - 1] = $border; 408 | } 409 | 410 | return $result; 411 | } 412 | 413 | private function prepareResult(): array 414 | { 415 | $result = []; 416 | 417 | /** @noinspection ForeachInvariantsInspection */ 418 | for ($i = 0; $i <= $this->rows; $i++) { 419 | $result[$i] = array_fill(0, $this->width, ' '); 420 | } 421 | 422 | return $result; 423 | } 424 | 425 | private function isPresent(array $markers, int $x): bool 426 | { 427 | return isset($markers[$x]) && ($markers[$x] !== null && $markers[$x] !== false); 428 | } 429 | 430 | private function processLinearGraph(array $result, array $markersData, int $x, int $y): array 431 | { 432 | $y1 = (int) (round($markersData[self::MARKERS][$x + 1] * $this->ratio) - $this->min2); 433 | if ($y === $y1) { 434 | $result[$this->rows - $y][$x + $this->offset] = $this->colorize('─', $this->currentColors); 435 | } else { 436 | if ($y > $y1) { 437 | $connectA = '╰'; 438 | $connectB = '╮'; 439 | 440 | $this->currentColors = $markersData[self::COLORS_DOWN]; 441 | } else { 442 | $connectA = '╭'; 443 | $connectB = '╯'; 444 | 445 | $this->currentColors = $markersData[self::COLORS]; 446 | } 447 | $result[$this->rows - $y1][$x + $this->offset] = $this->colorize($connectA, $this->currentColors); 448 | $result[$this->rows - $y][$x + $this->offset] = $this->colorize($connectB, $this->currentColors); 449 | 450 | $from = min($y, $y1); 451 | $to = max($y, $y1); 452 | for ($i = $from + 1; $i < $to; $i++) { 453 | $result[$this->rows - $i][$x + $this->offset] = $this->colorize('│', $this->currentColors); 454 | } 455 | } 456 | 457 | return $result; 458 | } 459 | 460 | private function processPoint(array $result, array $markersData, int $y, int $x): array 461 | { 462 | if ($markersData[self::POINT] === self::CROSS) { 463 | for ($i = 0; $i <= $this->width - $this->offset - 2; $i++) { 464 | $result[$this->rows - $y][$i + $this->offset] = $this->colorize('╌', $this->currentColors); 465 | } 466 | for ($i = 0; $i <= $this->rows; $i++) { 467 | $result[$this->rows - $i][$x + $this->offset] = $this->colorize('╎', $this->currentColors); 468 | } 469 | } 470 | 471 | $result[$this->rows - $y][$x + $this->offset] = $this->colorize('o', $this->currentColors); 472 | 473 | return $result; 474 | } 475 | 476 | private function processLine(array $result, int $y, string $lineStyle): array 477 | { 478 | $line = '╌'; 479 | if ($lineStyle === self::FULL_LINE) { 480 | $line = '─'; 481 | } 482 | 483 | for ($i = 0; $i <= $this->width - $this->offset - 2; $i++) { 484 | $result[$this->rows - $y][$i + $this->offset] = $this->colorize($line, $this->currentColors); 485 | } 486 | 487 | return $result; 488 | } 489 | 490 | private function deadjust(float $number): float 491 | { 492 | if ($this->adjuster !== null) { 493 | $number /= $this->adjuster; 494 | } 495 | 496 | return $number; 497 | } 498 | 499 | private function colorize(string $label, ?array $currentColors = null): string 500 | { 501 | if ($this->colorizer === null) { 502 | $this->colorizer = $this->getSettings()->getColorizer(); 503 | } 504 | 505 | return $this->colorizer->colorize($label, $currentColors); 506 | } 507 | } 508 | --------------------------------------------------------------------------------