1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* League.Period (https://period.thephpleague.com) |
5
|
|
|
* |
6
|
|
|
* (c) Ignace Nyamagana Butera <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
declare(strict_types=1); |
13
|
|
|
|
14
|
|
|
namespace League\Period\Chart; |
15
|
|
|
|
16
|
|
|
use League\Period\Period; |
17
|
|
|
use League\Period\Sequence; |
18
|
|
|
use function array_fill; |
19
|
|
|
use function array_splice; |
20
|
|
|
use function ceil; |
21
|
|
|
use function count; |
22
|
|
|
use function floor; |
23
|
|
|
use function implode; |
24
|
|
|
use function str_pad; |
25
|
|
|
use function str_repeat; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* A class to output a Dataset via a Gantt Bar graph. |
29
|
|
|
*/ |
30
|
|
|
final class GanttChart implements Chart |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* @var GanttChartConfig |
34
|
|
|
*/ |
35
|
|
|
private $config; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var float |
39
|
|
|
*/ |
40
|
|
|
private $start; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var float |
44
|
|
|
*/ |
45
|
|
|
private $unit; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* New instance. |
49
|
|
|
* |
50
|
|
|
* @param ?GanttChartConfig $config |
51
|
|
|
*/ |
52
|
3 |
|
public function __construct(?GanttChartConfig $config = null) |
53
|
|
|
{ |
54
|
3 |
|
$this->config = $config ?? new GanttChartConfig(); |
55
|
3 |
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @inheritDoc |
59
|
|
|
* |
60
|
|
|
* The generated Gantt Bar can be represented like the following but depends on the configuration used |
61
|
|
|
* |
62
|
|
|
* A [--------) |
63
|
|
|
* B [--) |
64
|
|
|
* C [-----) |
65
|
|
|
* D [---------------) |
66
|
|
|
* RESULT [-) [--) [-) |
67
|
|
|
*/ |
68
|
12 |
|
public function stroke(Data $dataset): void |
69
|
|
|
{ |
70
|
12 |
|
$this->setChartScale($dataset); |
71
|
12 |
|
$padding = $this->config->labelAlign(); |
72
|
12 |
|
$gap = str_repeat(' ', $this->config->gapSize()); |
73
|
12 |
|
$leftMargin = str_repeat(' ', $this->config->leftMarginSize()); |
74
|
12 |
|
$lineCharacters = array_fill(0, $this->config->width(), $this->config->space()); |
75
|
12 |
|
$labelMaxLength = $dataset->labelMaxLength(); |
76
|
12 |
|
$colorCodeIndexes = $this->config->colors(); |
77
|
12 |
|
$colorCodeCount = count($colorCodeIndexes); |
78
|
12 |
|
$output = $this->config->output(); |
79
|
12 |
|
foreach ($dataset as $offset => [$label, $item]) { |
80
|
9 |
|
$colorIndex = $colorCodeIndexes[$offset % $colorCodeCount]; |
81
|
9 |
|
$labelPortion = str_pad($label, $labelMaxLength, ' ', $padding); |
82
|
9 |
|
$dataPortion = $this->drawDataPortion($item, $lineCharacters); |
83
|
9 |
|
$output->writeln($leftMargin.$labelPortion.$gap.$dataPortion, $colorIndex); |
84
|
|
|
} |
85
|
12 |
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Sets the scale to render the line. |
89
|
|
|
*/ |
90
|
12 |
|
private function setChartScale(Data $dataset): void |
91
|
|
|
{ |
92
|
12 |
|
$this->start = 0; |
93
|
12 |
|
$this->unit = 1; |
94
|
12 |
|
$boundaries = $dataset->boundaries(); |
95
|
12 |
|
if (null !== $boundaries) { |
96
|
6 |
|
$this->start = $boundaries->getStartDate()->getTimestamp(); |
97
|
6 |
|
$this->unit = $this->config->width() / $boundaries->getTimestampInterval(); |
98
|
|
|
} |
99
|
12 |
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Convert a Dataset item into a graph data portion. |
103
|
|
|
* |
104
|
|
|
* @param string[] $lineCharacters |
105
|
|
|
*/ |
106
|
9 |
|
private function drawDataPortion(Sequence $item, array $lineCharacters): string |
107
|
|
|
{ |
108
|
|
|
$reducer = function (array $lineCharacters, Period $period): array { |
109
|
6 |
|
$startIndex = (int) floor(($period->getStartDate()->getTimestamp() - $this->start) * $this->unit); |
110
|
6 |
|
$endIndex = (int) ceil(($period->getEndDate()->getTimestamp() - $this->start) * $this->unit); |
111
|
6 |
|
$periodLength = $endIndex - $startIndex; |
112
|
|
|
|
113
|
6 |
|
array_splice($lineCharacters, $startIndex, $periodLength, array_fill(0, $periodLength, $this->config->body())); |
114
|
6 |
|
$lineCharacters[$startIndex] = $period->isStartIncluded() ? $this->config->startIncluded() : $this->config->startExcluded(); |
115
|
6 |
|
$lineCharacters[$endIndex - 1] = $period->isEndIncluded() ? $this->config->endIncluded() : $this->config->endExcluded(); |
116
|
|
|
|
117
|
6 |
|
return $lineCharacters; |
118
|
9 |
|
}; |
119
|
|
|
|
120
|
9 |
|
return implode('', $item->reduce($reducer, $lineCharacters)); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|