bakame / period-visualizer
A Visualizer for League Period.
Requires
- php: ^7.2
- ext-json: *
- league/period: ^4.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.0
- phpstan/phpstan: ^0.11.1
- phpstan/phpstan-phpunit: ^0.11.0
- phpstan/phpstan-strict-rules: ^0.11.0
- phpunit/phpunit: ^8.1
Suggests
- league/climate: to implement a better CLI console output
- symfony/console: to implement a better CLI console output
README
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 (------] [---------------]
System Requirements
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
Installation
$ composer require bakame/period-visualizer
Usage
Basic Usage
Generate a simple graph.
To 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
0
represents the label - the value at key
1
is aLeague\Period\Period
or aLeague\Period\Sequence
object
<?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 [-------------------------)
Appending items to display
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.
Setting the Dataset labels
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 aSequence
object; - The
Dataset::withLabels
to 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:
LatinLetter
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 letter
DecimalNumber
Generates 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 number
RomanNumber
Uses 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.
AffixLabel
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 suffix
ReverseLabel
Uses 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. [----------)
Custom LabelGenerator
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. [----------)
Displaying the Dataset
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 [------------------------------)
Customized the graph looks
The GanttChart
class can be customized by providing a GanttChartConfig
which defines:
- the output medium via a
OutputWriter
implementing 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.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Contributions are welcome and will be fully credited. Please see CONTRIBUTING for details.
Testing
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 test
Security
If you discover any security related issues, please email nyamsprod@gmail.com instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.