|
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
|
|
|
|