This package contains a visualizer for League Period.
It is inspired from the work of @thecrypticace on the following PR Visualization Helper.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use League\Period\Datepoint;
use League\Period\Period;
use League\Period\Sequence;
$sequence = new Sequence(
    Datepoint::create('2018-11-29')->getYear(Period::EXCLUDE_START_INCLUDE_END),
    Datepoint::create('2018-05-29')->getMonth()->expand('3 MONTH'),
    Datepoint::create('2017-01-13')->getQuarter(Period::EXCLUDE_ALL),
    Period::around('2016-06-01', '3 MONTHS', Period::INCLUDE_ALL)
);
$dataset = Dataset::fromSequence($sequence);
$dataset->append('gaps', $sequence->gaps());
(new GanttChart())->stroke($dataset);results:
 A                                          (--------------------]
 B                                            [-----------)       
 C                     (----)                                     
 D    [---------]                                                 
 gaps           (------]    [---------------]  You need:
- PHP >= 7.2 but the latest stable version of PHP is recommended
 - League/Period 4.4+ but the latest stable version is recommended
 
$ composer require bakame/period-visualizerTo generate a graph you need to give to the Dataset constructor a list of pairs. Each pair is an array containing 2 values:
- the value at key 
0represents the label - the value at key 
1is aLeague\Period\Periodor aLeague\Period\Sequenceobject 
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use League\Period\Period;
$dataset = new Dataset([
    ['A', new Period('2018-01-01', '2018-02-01')],
    ['B', new Period('2018-01-15', '2018-02-01')], 
]);
(new GanttChart())->stroke($dataset);results:
 A [----------------------------------------------)
 B                      [-------------------------)If you want to display a Sequence and some of its operations. You can append the operation results using the Dataset::append method.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use League\Period\Period;
use League\Period\Sequence;
$sequence = new Sequence(
    new Period('2018-01-01', '2018-03-01'),
    new Period('2018-05-01', '2018-08-01')
);
$dataset = new Dataset();
$dataset->append('A', $sequence[0]);
$dataset->append('B', $sequence[1]);
$dataset->append('GAPS', $sequence->gaps());
(new GanttChart())->stroke($dataset);results:
 A    [-------------)                                                         
 B                               [----------------)
 GAPS               [------------)    The Dataset implements the Countable and the IteratorAggregate interface. It also exposes the following methods:
<?php
public function Dataset::fromSequence(Sequence $sequence, ?LabelGenerator $labelGenerator = null): self; //Creates a new Dataset from a Sequence and a LabelGenerator.
public function Dataset::fromCollection(iterable $collection): self; //Creates a new Dataset from a generic iterable structure.
public function Dataset::appendAll(iterable $pairs): void; //adds multiple pairs at once.
public function Dataset::isEmpty(): bool; //Tells whether the collection is empty.
public function Dataset::labels(): string[]; //the current labels used
public function Dataset::items(): Sequence[]; //the current objects inside the Dataset
public function Dataset::boundaries(): ?Period;  //Returns the collection boundaries or null if it is empty.
public function Dataset::labelMaxLength(): int;  //Returns the label max length.
public function Dataset::withLabels(LabelGenerator $labelGenerator): self; //Update the labels used for the dataset.By default you are required to provide a label per item added present in a Dataset object.
The package provides a LabelGenerator interface that ease generating and creating labels for your visualization.
A LabelGenerator implementing class is needed for the following methods
- The 
Dataset::fromSequence, to create a new instance from aSequenceobject; - The 
Dataset::withLabelsto update the associated labels in the current instance; 
By default when using Dataset::fromSequence if no LabelGenerator class is supplied the LatinLetter label generator will be used.
The current package comes bundle with the following LabelGenerator implementing class:
Generates labels according the the latin alphabet.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\LatinLetter;
use League\Period\Period;
use League\Period\Sequence;
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    new LatinLetter('aa')
);
(new GanttChart())->stroke($dataset);results:
 aa [-----------------------------------)
 ab [----------)The LatinLetter also exposes the following methods:
<?php
public function LatinLetter::startingAt(): string; //returns the first letter to be used
public function LatinLetter::startsWith(): self;  //returns a new object with a new starting letterGenerates labels according to the decimal number system.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\DecimalNumber;
use Bakame\Period\Visualizer\GanttChart;
use League\Period\Period;
use League\Period\Sequence;
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    new DecimalNumber(42)
);
(new GanttChart())->stroke($dataset);results:
 42 [-----------------------------------)
 43 [----------)The DecimalNumber also exposes the following methods:
<?php
public function DecimalNumber::startingAt(): string; //returns the first decimal number to be used
public function DecimalNumber::startsWith(): self;  //returns a new object with a new starting decimal numberUses the DecimalNumber label generator class to generate Roman number labels.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\DecimalNumber;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\RomanNumber;
use League\Period\Period;
use League\Period\Sequence;
$labelGenerator = new RomanNumber(new DecimalNumber(5), RomanNumber::LOWER);
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    $labelGenerator
);
(new GanttChart())->stroke($dataset);results:
 v  [-----------------------------------)
 vi [----------)The RomanNumber also exposes the following methods:
<?php
const RomanNumber::UPPER = 1;
const RomanNumber::LOWER = 2;
public function RomanNumber::startingAt(): string; //returns the first decimal number to be used
public function RomanNumber::startsWith(): self;  //returns a new object with a new starting decimal number
public function RomanNumber::withLetterCase(int $lettercase): self;  //returns a new object with a new letter casing
public function RomanNumber::isUpper(): bool;  //Tells whether the roman letter is upper cased.
public function RomanNumber::isLower(): bool;  //Tells whether the roman letter is lower cased.Uses any labelGenerator implementing class to add prefix and/or suffix string to the generated labels.
<?php
use Bakame\Period\Visualizer\AffixLabel;
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\DecimalNumber;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\RomanNumber;
use League\Period\Period;
use League\Period\Sequence;
$labelGenerator = new AffixLabel(
    new RomanNumber(new DecimalNumber(5), RomanNumber::LOWER),
    '*', //prefix
    '.)'    //suffix
);
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    $labelGenerator
);
(new GanttChart())->stroke($dataset);results:
 * v .)  [-----------------------------------)
 * vi .) [----------)The AffixLabel also exposes the following methods:
<?php
public function AffixLabel::prefix(): string; //returns the current prefix
public function AffixLabel::suffix(): string;  //returns the current suffix
public function AffixLabel::withPrefix(string $prefix): self;  //returns a new object with a new prefix
public function AffixLabel::withSuffix(string $suffix): self;  //returns a new object with a new suffixUses any labelGenerator implementing class to reverse the generated labels order.
<?php
use Bakame\Period\Visualizer\AffixLabel;
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\DecimalNumber;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\ReverseLabel;
use Bakame\Period\Visualizer\RomanNumber;
use League\Period\Period;
use League\Period\Sequence;
$labelGenerator = new DecimalNumber(5);
$labelGenerator = new RomanNumber($labelGenerator, RomanNumber::LOWER);
$labelGenerator = new AffixLabel($labelGenerator, '', '.');
$labelGenerator = new ReverseLabel($labelGenerator);
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    $labelGenerator
);
(new GanttChart())->stroke($dataset);results:
 vi. [-----------------------------------)
 v.  [----------)You can create your own label generator by implementing the Bakame\Period\Visualizer\LabelGenerator interface like shown below:
<?php
use Bakame\Period\Visualizer\AffixLabel;
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\LabelGenerator;
use League\Period\Period;
use League\Period\Sequence;
$samelabel = new class implements LabelGenerator {
    public function generate(int $nbLabels): array
    {
        return array_fill(0, $nbLabels, $this->format('foobar'));
    }
        
    public function format($str): string
    {
        return (string) $str;
    }
};
$labelGenerator = new AffixLabel($samelabel, '', '.');
$dataset = Dataset::fromSequence(
    new Sequence(new Period('2018-01-01', '2018-02-01'), new Period('2018-01-15', '2018-02-01')),
    $labelGenerator
);
(new GanttChart())->stroke($dataset);results:
 foobar. [-----------------------------------)
 foobar. [----------)The GanttChart class is responsible for generating the graph from the Dataset by implementing the Graph interface for the console.
The GanttChart::stroke methods expects a Dataset object as its unique argument.
If you wish to present the graph on another medium like a web browser or an image, you will need to implement the interface for your implementation.
<?php
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\GanttChart;
use League\Period\Period;
$graph = new GanttChart();
$graph->stroke(new Dataset([
    ['first', new Period('2018-01-01 08:00:00', '2018-01-01 12:00:00')],
    ['last', new Period('2018-01-01 10:00:00', '2018-01-01 14:00:00')],
]));results:
 first [---------------------------)
 last            [------------------------------)The GanttChart class can be customized by providing a GanttChartConfig which defines:
- the output medium via a 
OutputWriterimplementing class. - the graph settings. (How the intervals will be stroked)
- sets the graph width
 - sets the graph colors
 - sets the gap between the labels and the rows
 - sets the label alignment
 
 - the output settings (How the intervals will be created)
- sets single characters to represent the boundary types
 - sets single characters to represent the body and space
 
 
You can easily create a OutputWriter implementing class with libraries like League CLImate or Symfony Console
to output the resulting graph. If you don't, the package ships with a minimal ConsoleOutput class which is used
if you do not provide you own implementation.
<?php
use Bakame\Period\Visualizer\AffixLabel;
use Bakame\Period\Visualizer\ConsoleOutput;
use Bakame\Period\Visualizer\Dataset;
use Bakame\Period\Visualizer\DecimalNumber;
use Bakame\Period\Visualizer\GanttChart;
use Bakame\Period\Visualizer\GanttChartConfig;
use Bakame\Period\Visualizer\ReverseLabel;
use Bakame\Period\Visualizer\RomanNumber;
use League\Period\Datepoint;
use League\Period\Period;
use League\Period\Sequence;
$config = GanttChartConfig::createFromRainbow()
    ->withOutput(new ConsoleOutput(STDOUT))
    ->withStartExcluded('π')
    ->withStartIncluded('π
')
    ->withEndExcluded('πΎ')
    ->withEndIncluded('π')
    ->withWidth(30)
    ->withSpace('π©')
    ->withBody('π')
    ->withGapSize(2)
    ->withLeftMarginSize(1)
    ->withLabelAlign(GanttChartConfig::ALIGN_RIGHT)
;
$labelGenerator = new DecimalNumber(42);
$labelGenerator = new RomanNumber($labelGenerator, RomanNumber::UPPER);
$labelGenerator = new AffixLabel($labelGenerator, '', '.');
$labelGenerator = new ReverseLabel($labelGenerator);
$sequence = new Sequence(
    Datepoint::create('2018-11-29')->getYear(Period::EXCLUDE_START_INCLUDE_END),
    Datepoint::create('2018-05-29')->getMonth()->expand('3 MONTH'),
    Datepoint::create('2017-01-13')->getQuarter(Period::EXCLUDE_ALL),
    Period::around('2016-06-01', '3 MONTHS', Period::INCLUDE_ALL)
);
$dataset = Dataset::fromSequence($sequence, $labelGenerator);
$dataset->append($labelGenerator->format('gaps'), $sequence->gaps());
$graph = new GanttChart($config);
$graph->stroke($dataset);result:
   XLV.  π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©πππππππππππ
  XLIV.  π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π
ππππππΎπ©π©π©
 XLIII.  π©π©π©π©π©π©π©π©ππππΎπ©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©
  XLII.  π
ππππππ©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©π©
  GAPS.  π©π©π©π©π©πππππ©π©π
πππππππππ©π©π©π©π©π©π©π©π©π©On a POSIX compliant console all lines have different colors
The GanttChartConfig class exposes the following additional constants and methods:
<?php
const GanttChartConfig::ALIGN_LEFT = 1;
const GanttChartConfig::ALIGN_RIGHT = 0;
const GanttChartConfig::ALIGN_CENTER = 2;
public function GanttChartConfig::__construct(OutputWriter $output);
public function GanttChartConfig::output(): OutputWriter;  //Returns the OutputWriter instance.
public function GanttChartConfig::startExcluded(): string; //Retrieves the excluded start block character.
public function GanttChartConfig::startIncluded(): string; //Retrieves the included start block character.
public function GanttChartConfig::endExcluded(): string;   //Retrieves the excluded end block character.
public function GanttChartConfig::endIncluded(): string;   //Retrieves the included end block character.
public function GanttChartConfig::width(): int;            //Retrieves the max size width.
public function GanttChartConfig::body(): string;          //Retrieves the body block character.
public function GanttChartConfig::space(): string;         //Retrieves the space block character.
public function GanttChartConfig::colors(): string[];      //The selected colors for each row.
public function GanttChartConfig::gapSize(): int;          //Retrieves the gap sequence between the label and the line.
public function GanttChartConfig::labelAlign(): int;       //Returns how label should be aligned.
public function GanttChartConfig::leftMarginSize(): int;   //Retrieves the margin between the label and the console left side.GanttChartConfig is immutable, modifying its properties returns a new instance with the updated values.
Please see CHANGELOG for more information on what has changed recently.
Contributions are welcome and will be fully credited. Please see CONTRIBUTING for details.
The library has a :
- a PHPUnit test suite
 - a coding style compliance test suite using PHP CS Fixer.
 - a code analysis compliance test suite using PHPStan.
 
To run the tests, run the following command from the project folder.
$ composer testIf you discover any security related issues, please email nyamsprod@gmail.com instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.