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-visualizer
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 [-------------------------)
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 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:
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
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
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.
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
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. [----------)
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
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.
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 test
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.