1 | <?php |
||
2 | |||
3 | /** |
||
4 | * JPGraph v4.0.3 |
||
5 | */ |
||
6 | |||
7 | namespace Amenadiel\JpGraph\Graph; |
||
8 | |||
9 | use Amenadiel\JpGraph\Image; |
||
10 | use Amenadiel\JpGraph\Plot; |
||
11 | use Amenadiel\JpGraph\Text; |
||
12 | use Amenadiel\JpGraph\Util; |
||
13 | |||
14 | /** |
||
15 | * @class GanttGraph |
||
16 | * // Description: Main class to handle gantt graphs |
||
17 | */ |
||
18 | class GanttGraph extends Graph |
||
19 | { |
||
20 | public $scale; // Public accessible |
||
21 | public $hgrid; |
||
22 | private $iObj = []; // Gantt objects |
||
23 | private $iLabelHMarginFactor = 0.2; // 10% margin on each side of the labels |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
24 | private $iLabelVMarginFactor = 0.4; // 40% margin on top and bottom of label |
||
25 | private $iLayout = GANTT_FROMTOP; // Could also be GANTT_EVEN |
||
26 | private $iSimpleFont = FF_FONT1; |
||
27 | private $iSimpleFontSize = 11; |
||
28 | private $iSimpleStyle = GANTT_RDIAG; |
||
29 | private $iSimpleColor = 'yellow'; |
||
30 | private $iSimpleBkgColor = 'red'; |
||
31 | private $iSimpleProgressBkgColor = 'gray'; |
||
32 | private $iSimpleProgressColor = 'darkgreen'; |
||
33 | private $iSimpleProgressStyle = GANTT_SOLID; |
||
34 | private $iZoomFactor = 1.0; |
||
35 | |||
36 | /** |
||
37 | * CONSTRUCTOR |
||
38 | * // Create a new gantt graph. |
||
39 | * |
||
40 | * @param mixed $aWidth |
||
41 | * @param mixed $aHeight |
||
42 | * @param mixed $aCachedName |
||
43 | * @param mixed $aTimeOut |
||
44 | * @param mixed $aInline |
||
45 | */ |
||
46 | public function __construct($aWidth = 0, $aHeight = 0, $aCachedName = '', $aTimeOut = 0, $aInline = true) |
||
47 | { |
||
48 | // Backward compatibility |
||
49 | if ($aWidth == -1) { |
||
50 | $aWidth = 0; |
||
51 | } |
||
52 | |||
53 | if ($aHeight == -1) { |
||
54 | $aHeight = 0; |
||
55 | } |
||
56 | |||
57 | if ($aWidth < 0 || $aHeight < 0) { |
||
58 | Util\JpGraphError::RaiseL(6002); |
||
59 | //("You can't specify negative sizes for Gantt graph dimensions. Use 0 to indicate that you want the library to automatically determine a dimension."); |
||
60 | } |
||
61 | parent::__construct($aWidth, $aHeight, $aCachedName, $aTimeOut, $aInline); |
||
62 | $this->scale = new GanttScale($this->img); |
||
63 | |||
64 | // Default margins |
||
65 | $this->img->SetMargin(15, 17, 25, 15); |
||
66 | |||
67 | $this->hgrid = new HorizontalGridLine(); |
||
68 | |||
69 | $this->scale->ShowHeaders(GANTT_HWEEK | GANTT_HDAY); |
||
70 | $this->SetBox(); |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * PUBLIC METHODS. |
||
75 | * |
||
76 | * @param mixed $aFont |
||
77 | * @param mixed $aSize |
||
78 | */ |
||
79 | public function SetSimpleFont($aFont, $aSize) |
||
80 | { |
||
81 | $this->iSimpleFont = $aFont; |
||
82 | $this->iSimpleFontSize = $aSize; |
||
83 | } |
||
84 | |||
85 | public function SetSimpleStyle($aBand, $aColor, $aBkgColor) |
||
86 | { |
||
87 | $this->iSimpleStyle = $aBand; |
||
88 | $this->iSimpleColor = $aColor; |
||
89 | $this->iSimpleBkgColor = $aBkgColor; |
||
90 | } |
||
91 | |||
92 | // A utility function to help create basic Gantt charts |
||
93 | public function CreateSimple($data, $constrains = [], $progress = []) |
||
94 | { |
||
95 | $num = safe_count($data); |
||
96 | for ($i = 0; $i < $num; ++$i) { |
||
97 | switch ($data[$i][1]) { |
||
98 | case ACTYPE_GROUP: |
||
99 | // Create a slightly smaller height bar since the |
||
100 | // "wings" at the end will make it look taller |
||
101 | $a = new Plot\GanttBar($data[$i][0], $data[$i][2], $data[$i][3], $data[$i][4], '', 8); |
||
102 | $a->title->SetFont($this->iSimpleFont, FS_BOLD, $this->iSimpleFontSize); |
||
103 | $a->rightMark->Show(); |
||
104 | $a->rightMark->SetType(MARK_RIGHTTRIANGLE); |
||
105 | $a->rightMark->SetWidth(8); |
||
106 | $a->rightMark->SetColor('black'); |
||
107 | $a->rightMark->SetFillColor('black'); |
||
108 | |||
109 | $a->leftMark->Show(); |
||
110 | $a->leftMark->SetType(MARK_LEFTTRIANGLE); |
||
111 | $a->leftMark->SetWidth(8); |
||
112 | $a->leftMark->SetColor('black'); |
||
113 | $a->leftMark->SetFillColor('black'); |
||
114 | |||
115 | $a->SetPattern(BAND_SOLID, 'black'); |
||
116 | $csimpos = 6; |
||
117 | |||
118 | break; |
||
119 | case ACTYPE_NORMAL: |
||
120 | $a = new Plot\GanttBar($data[$i][0], $data[$i][2], $data[$i][3], $data[$i][4], '', 10); |
||
121 | $a->title->SetFont($this->iSimpleFont, FS_NORMAL, $this->iSimpleFontSize); |
||
122 | $a->SetPattern($this->iSimpleStyle, $this->iSimpleColor); |
||
123 | $a->SetFillColor($this->iSimpleBkgColor); |
||
124 | // Check if this activity should have a constrain line |
||
125 | $n = safe_count($constrains); |
||
126 | for ($j = 0; $j < $n; ++$j) { |
||
127 | if (empty($constrains[$j]) || (safe_count($constrains[$j]) != 3)) { |
||
128 | Util\JpGraphError::RaiseL(6003, $j); |
||
129 | //("Invalid format for Constrain parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Constrain-To,Constrain-Type)"); |
||
130 | } |
||
131 | if ($constrains[$j][0] == $data[$i][0]) { |
||
132 | $a->SetConstrain($constrains[$j][1], $constrains[$j][2], 'black', ARROW_S2, ARROWT_SOLID); |
||
133 | } |
||
134 | } |
||
135 | |||
136 | // Check if this activity have a progress bar |
||
137 | $n = safe_count($progress); |
||
138 | for ($j = 0; $j < $n; ++$j) { |
||
139 | if (empty($progress[$j]) || (safe_count($progress[$j]) != 2)) { |
||
140 | Util\JpGraphError::RaiseL(6004, $j); |
||
141 | //("Invalid format for Progress parameter at index=$j in CreateSimple(). Parameter must start with index 0 and contain arrays of (Row,Progress)"); |
||
142 | } |
||
143 | if ($progress[$j][0] == $data[$i][0]) { |
||
144 | $a->progress->Set($progress[$j][1]); |
||
145 | $a->progress->SetPattern( |
||
146 | $this->iSimpleProgressStyle, |
||
147 | $this->iSimpleProgressColor |
||
148 | ); |
||
149 | $a->progress->SetFillColor($this->iSimpleProgressBkgColor); |
||
150 | //$a->progress->SetPattern($progress[$j][2],$progress[$j][3]); |
||
151 | break; |
||
152 | } |
||
153 | } |
||
154 | $csimpos = 6; |
||
155 | |||
156 | break; |
||
157 | case ACTYPE_MILESTONE: |
||
158 | $a = new Plot\MileStone($data[$i][0], $data[$i][2], $data[$i][3]); |
||
159 | $a->title->SetFont($this->iSimpleFont, FS_NORMAL, $this->iSimpleFontSize); |
||
160 | $a->caption->SetFont($this->iSimpleFont, FS_NORMAL, $this->iSimpleFontSize); |
||
161 | $csimpos = 5; |
||
162 | |||
163 | break; |
||
164 | default: |
||
165 | die('Unknown activity type'); |
||
0 ignored issues
–
show
|
|||
166 | |||
167 | break; |
||
168 | } |
||
169 | |||
170 | // Setup caption |
||
171 | $a->caption->Set($data[$i][$csimpos - 1]); |
||
172 | |||
173 | // Check if this activity should have a CSIM target�? |
||
174 | if (!empty($data[$i][$csimpos])) { |
||
175 | $a->SetCSIMTarget($data[$i][$csimpos]); |
||
176 | $a->SetCSIMAlt($data[$i][$csimpos + 1]); |
||
177 | } |
||
178 | if (!empty($data[$i][$csimpos + 2])) { |
||
179 | $a->title->SetCSIMTarget($data[$i][$csimpos + 2]); |
||
180 | $a->title->SetCSIMAlt($data[$i][$csimpos + 3]); |
||
181 | } |
||
182 | |||
183 | $this->Add($a); |
||
184 | } |
||
185 | } |
||
186 | |||
187 | // Set user specified scale zoom factor when auto sizing is used |
||
188 | public function SetZoomFactor($aZoom) |
||
189 | { |
||
190 | $this->iZoomFactor = $aZoom; |
||
191 | } |
||
192 | |||
193 | // Set what headers should be shown |
||
194 | public function ShowHeaders($aFlg) |
||
195 | { |
||
196 | $this->scale->ShowHeaders($aFlg); |
||
197 | } |
||
198 | |||
199 | // Specify the fraction of the font height that should be added |
||
200 | // as vertical margin |
||
201 | public function SetLabelVMarginFactor($aVal) |
||
202 | { |
||
203 | $this->iLabelVMarginFactor = $aVal; |
||
204 | } |
||
205 | |||
206 | // Synonym to the method above |
||
207 | public function SetVMarginFactor($aVal) |
||
208 | { |
||
209 | $this->iLabelVMarginFactor = $aVal; |
||
210 | } |
||
211 | |||
212 | // Add a new Gantt object |
||
213 | public function Add($aObject) |
||
214 | { |
||
215 | if (is_array($aObject) && safe_count($aObject) > 0) { |
||
216 | $cl = $aObject[0]; |
||
217 | if (($cl instanceof Plot\IconPlot)) { |
||
218 | $this->AddIcon($aObject); |
||
219 | } elseif (($cl instanceof Text\Text)) { |
||
220 | $this->AddText($aObject); |
||
221 | } else { |
||
222 | $n = safe_count($aObject); |
||
223 | for ($i = 0; $i < $n; ++$i) { |
||
224 | $this->iObj[] = $aObject[$i]; |
||
225 | } |
||
226 | } |
||
227 | } else { |
||
228 | if (($aObject instanceof Plot\IconPlot)) { |
||
229 | $this->AddIcon($aObject); |
||
230 | } elseif (($aObject instanceof Text\Text)) { |
||
231 | $this->AddText($aObject); |
||
232 | } else { |
||
233 | $this->iObj[] = $aObject; |
||
234 | } |
||
235 | } |
||
236 | } |
||
237 | |||
238 | public function StrokeTexts() |
||
239 | { |
||
240 | // Stroke any user added text objects |
||
241 | if ($this->texts != null) { |
||
242 | $n = safe_count($this->texts); |
||
243 | for ($i = 0; $i < $n; ++$i) { |
||
244 | if ($this->texts[$i]->iScalePosX !== null && $this->texts[$i]->iScalePosY !== null) { |
||
245 | $x = $this->scale->TranslateDate($this->texts[$i]->iScalePosX); |
||
246 | $y = $this->scale->TranslateVertPos($this->texts[$i]->iScalePosY); |
||
247 | $y -= $this->scale->GetVertSpacing() / 2; |
||
248 | } else { |
||
249 | $x = $y = null; |
||
250 | } |
||
251 | $this->texts[$i]->Stroke($this->img, $x, $y); |
||
252 | } |
||
253 | } |
||
254 | } |
||
255 | |||
256 | // Override inherit method from Graph and give a warning message |
||
257 | public function SetScale($aAxisType, $aYMin = 1, $aYMax = 1, $aXMin = 1, $aXMax = 1) |
||
258 | { |
||
259 | Util\JpGraphError::RaiseL(6005); |
||
260 | //("SetScale() is not meaningfull with Gantt charts."); |
||
261 | } |
||
262 | |||
263 | // Specify the date range for Gantt graphs (if this is not set it will be |
||
264 | // automtically determined from the input data) |
||
265 | public function SetDateRange($aStart, $aEnd) |
||
266 | { |
||
267 | // Adjust the start and end so that the indicate the |
||
268 | // begining and end of respective start and end days |
||
269 | if (strpos($aStart, ':') === false) { |
||
270 | $aStart = date('Y-m-d 00:00', strtotime($aStart)); |
||
271 | } |
||
272 | |||
273 | if (strpos($aEnd, ':') === false) { |
||
274 | $aEnd = date('Y-m-d 23:59', strtotime($aEnd)); |
||
275 | } |
||
276 | |||
277 | $this->scale->SetRange($aStart, $aEnd); |
||
278 | } |
||
279 | |||
280 | // Get the maximum width of the activity titles columns for the bars |
||
281 | // The name is lightly misleading since we from now on can have |
||
282 | // multiple columns in the label section. When this was first written |
||
283 | // it only supported a single label, hence the name. |
||
284 | public function GetMaxLabelWidth() |
||
285 | { |
||
286 | $m = 10; |
||
287 | if ($this->iObj != null) { |
||
288 | $marg = $this->scale->actinfo->iLeftColMargin + $this->scale->actinfo->iRightColMargin; |
||
0 ignored issues
–
show
|
|||
289 | $n = safe_count($this->iObj); |
||
290 | for ($i = 0; $i < $n; ++$i) { |
||
291 | if (!empty($this->iObj[$i]->title)) { |
||
292 | if ($this->iObj[$i]->title->HasTabs()) { |
||
293 | list($tot, $w) = $this->iObj[$i]->title->GetWidth($this->img, true); |
||
294 | $m = max($m, $tot); |
||
295 | } else { |
||
296 | $m = max($m, $this->iObj[$i]->title->GetWidth($this->img)); |
||
297 | } |
||
298 | } |
||
299 | } |
||
300 | } |
||
301 | |||
302 | return $m; |
||
303 | } |
||
304 | |||
305 | // Get the maximum height of the titles for the bars |
||
306 | public function GetMaxLabelHeight() |
||
307 | { |
||
308 | $m = 10; |
||
309 | if ($this->iObj != null) { |
||
310 | $n = safe_count($this->iObj); |
||
311 | // We can not include the title of GnttVLine since that title is stroked at the bottom |
||
312 | // of the Gantt bar and not in the activity title columns |
||
313 | for ($i = 0; $i < $n; ++$i) { |
||
314 | if (!empty($this->iObj[$i]->title) && !($this->iObj[$i] instanceof Plot\GanttVLine)) { |
||
315 | $m = max($m, $this->iObj[$i]->title->GetHeight($this->img)); |
||
316 | } |
||
317 | } |
||
318 | } |
||
319 | |||
320 | return $m; |
||
321 | } |
||
322 | |||
323 | public function GetMaxBarAbsHeight() |
||
324 | { |
||
325 | $m = 0; |
||
326 | if ($this->iObj != null) { |
||
327 | $m = $this->iObj[0]->GetAbsHeight($this->img); |
||
328 | $n = safe_count($this->iObj); |
||
329 | for ($i = 1; $i < $n; ++$i) { |
||
330 | $m = max($m, $this->iObj[$i]->GetAbsHeight($this->img)); |
||
331 | } |
||
332 | } |
||
333 | |||
334 | return $m; |
||
335 | } |
||
336 | |||
337 | // Get the maximum used line number (vertical position) for bars |
||
338 | public function GetBarMaxLineNumber() |
||
339 | { |
||
340 | $m = 1; |
||
341 | if ($this->iObj != null) { |
||
342 | $m = $this->iObj[0]->GetLineNbr(); |
||
343 | $n = safe_count($this->iObj); |
||
344 | for ($i = 1; $i < $n; ++$i) { |
||
345 | $m = max($m, $this->iObj[$i]->GetLineNbr()); |
||
346 | } |
||
347 | } |
||
348 | |||
349 | return $m; |
||
350 | } |
||
351 | |||
352 | // Get the minumum and maximum used dates for all bars |
||
353 | public function GetBarMinMax() |
||
354 | { |
||
355 | $start = 0; |
||
356 | $n = safe_count($this->iObj); |
||
357 | while ($start < $n && $this->iObj[$start]->GetMaxDate() === false) { |
||
358 | ++$start; |
||
359 | } |
||
360 | |||
361 | if ($start >= $n) { |
||
362 | Util\JpGraphError::RaiseL(6006); |
||
363 | //('Cannot autoscale Gantt chart. No dated activities exist. [GetBarMinMax() start >= n]'); |
||
364 | } |
||
365 | |||
366 | $max = $this->scale->NormalizeDate($this->iObj[$start]->GetMaxDate()); |
||
367 | $min = $this->scale->NormalizeDate($this->iObj[$start]->GetMinDate()); |
||
368 | |||
369 | for ($i = $start + 1; $i < $n; ++$i) { |
||
370 | $rmax = $this->scale->NormalizeDate($this->iObj[$i]->GetMaxDate()); |
||
371 | if ($rmax != false) { |
||
372 | $max = max($max, $rmax); |
||
373 | } |
||
374 | |||
375 | $rmin = $this->scale->NormalizeDate($this->iObj[$i]->GetMinDate()); |
||
376 | if ($rmin != false) { |
||
377 | $min = min($min, $rmin); |
||
378 | } |
||
379 | } |
||
380 | $minDate = date('Y-m-d', $min); |
||
381 | $min = strtotime($minDate); |
||
382 | $maxDate = date('Y-m-d 23:59', $max); |
||
383 | $max = strtotime($maxDate); |
||
384 | |||
385 | return [$min, $max]; |
||
386 | } |
||
387 | |||
388 | // Create a new auto sized canvas if the user hasn't specified a size |
||
389 | // The size is determined by what scale the user has choosen and hence |
||
390 | // the minimum width needed to display the headers. Some margins are |
||
391 | // also added to make it better looking. |
||
392 | public function AutoSize() |
||
393 | { |
||
394 | if ($this->img->img == null) { |
||
395 | // The predefined left, right, top, bottom margins. |
||
396 | // Note that the top margin might incease depending on |
||
397 | // the title. |
||
398 | $hadj = $vadj = 0; |
||
399 | if ($this->doshadow) { |
||
400 | $hadj = $this->shadow_width; |
||
401 | $vadj = $this->shadow_width + 5; |
||
402 | } |
||
403 | |||
404 | $lm = $this->img->left_margin; |
||
405 | $rm = $this->img->right_margin + $hadj; |
||
406 | $rm += 2; |
||
407 | $tm = $this->img->top_margin; |
||
408 | $bm = $this->img->bottom_margin + $vadj; |
||
409 | $bm += 2; |
||
410 | |||
411 | // If there are any added Plot\GanttVLine we must make sure that the |
||
412 | // bottom margin is wide enough to hold a title. |
||
413 | $n = safe_count($this->iObj); |
||
414 | for ($i = 0; $i < $n; ++$i) { |
||
415 | if ($this->iObj[$i] instanceof Plot\GanttVLine) { |
||
416 | $bm = max($bm, $this->iObj[$i]->title->GetHeight($this->img) + 10); |
||
417 | } |
||
418 | } |
||
419 | |||
420 | // First find out the height |
||
421 | $n = $this->GetBarMaxLineNumber() + 1; |
||
422 | $m = max($this->GetMaxLabelHeight(), $this->GetMaxBarAbsHeight()); |
||
423 | $height = $n * ((1 + $this->iLabelVMarginFactor) * $m); |
||
424 | |||
425 | // Add the height of the scale titles |
||
426 | $h = $this->scale->GetHeaderHeight(); |
||
427 | $height += $h; |
||
428 | |||
429 | // Calculate the top margin needed for title and subtitle |
||
430 | if ($this->title->t != '') { |
||
431 | $tm += $this->title->GetFontHeight($this->img); |
||
432 | } |
||
433 | if ($this->subtitle->t != '') { |
||
434 | $tm += $this->subtitle->GetFontHeight($this->img); |
||
435 | } |
||
436 | |||
437 | // ...and then take the bottom and top plot margins into account |
||
438 | $height += $tm + $bm + $this->scale->iTopPlotMargin + $this->scale->iBottomPlotMargin; |
||
439 | // Now find the minimum width for the chart required |
||
440 | |||
441 | // If day scale or smaller is shown then we use the day font width |
||
442 | // as the base size unit. |
||
443 | // If only weeks or above is displayed we use a modified unit to |
||
444 | // get a smaller image. |
||
445 | if ($this->scale->IsDisplayHour() || $this->scale->IsDisplayMinute()) { |
||
446 | // Add 2 pixel margin on each side |
||
447 | $fw = $this->scale->day->GetFontWidth($this->img) + 4; |
||
448 | } elseif ($this->scale->IsDisplayWeek()) { |
||
449 | $fw = 8; |
||
450 | } elseif ($this->scale->IsDisplayMonth()) { |
||
451 | $fw = 4; |
||
452 | } else { |
||
453 | $fw = 2; |
||
454 | } |
||
455 | |||
456 | $nd = $this->scale->GetNumberOfDays(); |
||
457 | |||
458 | if ($this->scale->IsDisplayDay()) { |
||
459 | // If the days are displayed we also need to figure out |
||
460 | // how much space each day's title will require. |
||
461 | switch ($this->scale->day->iStyle) { |
||
462 | case DAYSTYLE_LONG: |
||
463 | $txt = 'Monday'; |
||
464 | |||
465 | break; |
||
466 | case DAYSTYLE_LONGDAYDATE1: |
||
467 | $txt = 'Monday 23 Jun'; |
||
468 | |||
469 | break; |
||
470 | case DAYSTYLE_LONGDAYDATE2: |
||
471 | $txt = 'Monday 23 Jun 2003'; |
||
472 | |||
473 | break; |
||
474 | case DAYSTYLE_SHORT: |
||
475 | $txt = 'Mon'; |
||
476 | |||
477 | break; |
||
478 | case DAYSTYLE_SHORTDAYDATE1: |
||
479 | $txt = 'Mon 23/6'; |
||
480 | |||
481 | break; |
||
482 | case DAYSTYLE_SHORTDAYDATE2: |
||
483 | $txt = 'Mon 23 Jun'; |
||
484 | |||
485 | break; |
||
486 | case DAYSTYLE_SHORTDAYDATE3: |
||
487 | $txt = 'Mon 23'; |
||
488 | |||
489 | break; |
||
490 | case DAYSTYLE_SHORTDATE1: |
||
491 | $txt = '23/6'; |
||
492 | |||
493 | break; |
||
494 | case DAYSTYLE_SHORTDATE2: |
||
495 | $txt = '23 Jun'; |
||
496 | |||
497 | break; |
||
498 | case DAYSTYLE_SHORTDATE3: |
||
499 | $txt = 'Mon 23'; |
||
500 | |||
501 | break; |
||
502 | case DAYSTYLE_SHORTDATE4: |
||
503 | $txt = '88'; |
||
504 | |||
505 | break; |
||
506 | case DAYSTYLE_CUSTOM: |
||
507 | $txt = date($this->scale->day->iLabelFormStr, strtotime('2003-12-20 18:00')); |
||
508 | |||
509 | break; |
||
510 | case DAYSTYLE_ONELETTER: |
||
511 | default: |
||
512 | $txt = 'M'; |
||
513 | |||
514 | break; |
||
515 | } |
||
516 | $fw = $this->scale->day->GetStrWidth($this->img, $txt) + 6; |
||
517 | } |
||
518 | |||
519 | // If we have hours enabled we must make sure that each day has enough |
||
520 | // space to fit the number of hours to be displayed. |
||
521 | if ($this->scale->IsDisplayHour()) { |
||
522 | // Depending on what format the user has choose we need different amount |
||
523 | // of space. We therefore create a typical string for the choosen format |
||
524 | // and determine the length of that string. |
||
525 | switch ($this->scale->hour->iStyle) { |
||
526 | case HOURSTYLE_HMAMPM: |
||
527 | $txt = '12:00pm'; |
||
528 | |||
529 | break; |
||
530 | case HOURSTYLE_H24: |
||
531 | // 13 |
||
532 | $txt = '24'; |
||
533 | |||
534 | break; |
||
535 | case HOURSTYLE_HAMPM: |
||
536 | $txt = '12pm'; |
||
537 | |||
538 | break; |
||
539 | case HOURSTYLE_CUSTOM: |
||
540 | $txt = date($this->scale->hour->iLabelFormStr, strtotime('2003-12-20 18:00')); |
||
541 | |||
542 | break; |
||
543 | case HOURSTYLE_HM24: |
||
544 | default: |
||
545 | $txt = '24:00'; |
||
546 | |||
547 | break; |
||
548 | } |
||
549 | |||
550 | $hfw = $this->scale->hour->GetStrWidth($this->img, $txt) + 6; |
||
551 | $mw = $hfw; |
||
552 | if ($this->scale->IsDisplayMinute()) { |
||
553 | // Depending on what format the user has choose we need different amount |
||
554 | // of space. We therefore create a typical string for the choosen format |
||
555 | // and determine the length of that string. |
||
556 | switch ($this->scale->minute->iStyle) { |
||
557 | case HOURSTYLE_CUSTOM: |
||
558 | $txt2 = date($this->scale->minute->iLabelFormStr, strtotime('2005-05-15 18:55')); |
||
559 | |||
560 | break; |
||
561 | case MINUTESTYLE_MM: |
||
562 | default: |
||
563 | $txt2 = '15'; |
||
564 | |||
565 | break; |
||
566 | } |
||
567 | |||
568 | $mfw = $this->scale->minute->GetStrWidth($this->img, $txt2) + 6; |
||
569 | $n2 = ceil(60 / $this->scale->minute->GetIntervall()); |
||
570 | $mw = $n2 * $mfw; |
||
571 | } |
||
572 | $hfw = $hfw < $mw ? $mw : $hfw; |
||
573 | $n = ceil(24 * 60 / $this->scale->TimeToMinutes($this->scale->hour->GetIntervall())); |
||
574 | $hw = $n * $hfw; |
||
575 | $fw = $fw < $hw ? $hw : $fw; |
||
576 | } |
||
577 | |||
578 | // We need to repeat this code block here as well. |
||
579 | // THIS iS NOT A MISTAKE ! |
||
580 | // We really need it since we need to adjust for minutes both in the case |
||
581 | // where hour scale is shown and when it is not shown. |
||
582 | |||
583 | if ($this->scale->IsDisplayMinute()) { |
||
584 | // Depending on what format the user has choose we need different amount |
||
585 | // of space. We therefore create a typical string for the choosen format |
||
586 | // and determine the length of that string. |
||
587 | switch ($this->scale->minute->iStyle) { |
||
588 | case HOURSTYLE_CUSTOM: |
||
589 | $txt = date($this->scale->minute->iLabelFormStr, strtotime('2005-05-15 18:55')); |
||
590 | |||
591 | break; |
||
592 | case MINUTESTYLE_MM: |
||
593 | default: |
||
594 | $txt = '15'; |
||
595 | |||
596 | break; |
||
597 | } |
||
598 | |||
599 | $mfw = $this->scale->minute->GetStrWidth($this->img, $txt) + 6; |
||
600 | $n = ceil(60 / $this->scale->TimeToMinutes($this->scale->minute->GetIntervall())); |
||
601 | $mw = $n * $mfw; |
||
602 | $fw = $fw < $mw ? $mw : $fw; |
||
603 | } |
||
604 | |||
605 | // If we display week we must make sure that 7*$fw is enough |
||
606 | // to fit up to 10 characters of the week font (if the week is enabled) |
||
607 | if ($this->scale->IsDisplayWeek()) { |
||
608 | // Depending on what format the user has choose we need different amount |
||
609 | // of space |
||
610 | $fsw = strlen($this->scale->week->iLabelFormStr); |
||
611 | if ($this->scale->week->iStyle == WEEKSTYLE_FIRSTDAY2WNBR) { |
||
612 | $fsw += 8; |
||
613 | } elseif ($this->scale->week->iStyle == WEEKSTYLE_FIRSTDAYWNBR) { |
||
614 | $fsw += 7; |
||
615 | } else { |
||
616 | $fsw += 4; |
||
617 | } |
||
618 | |||
619 | $ww = $fsw * $this->scale->week->GetFontWidth($this->img); |
||
620 | if (7 * $fw < $ww) { |
||
621 | $fw = ceil($ww / 7); |
||
622 | } |
||
623 | } |
||
624 | |||
625 | if (!$this->scale->IsDisplayDay() && !$this->scale->IsDisplayHour() && |
||
626 | !(($this->scale->week->iStyle == WEEKSTYLE_FIRSTDAYWNBR || |
||
627 | $this->scale->week->iStyle == WEEKSTYLE_FIRSTDAY2WNBR) && $this->scale->IsDisplayWeek())) { |
||
628 | // If we don't display the individual days we can shrink the |
||
629 | // scale a little bit. This is a little bit pragmatic at the |
||
630 | // moment and should be re-written to take into account |
||
631 | // a) What scales exactly are shown and |
||
632 | // b) what format do they use so we know how wide we need to |
||
633 | // make each scale text space at minimum. |
||
634 | $fw /= 2; |
||
635 | if (!$this->scale->IsDisplayWeek()) { |
||
636 | $fw /= 1.8; |
||
637 | } |
||
638 | } |
||
639 | |||
640 | $cw = $this->GetMaxActInfoColWidth(); |
||
641 | $this->scale->actinfo->SetMinColWidth($cw); |
||
642 | if ($this->img->width <= 0) { |
||
643 | // Now determine the width for the activity titles column |
||
644 | |||
645 | // Firdst find out the maximum width of each object column |
||
646 | $titlewidth = max( |
||
647 | max( |
||
648 | $this->GetMaxLabelWidth(), |
||
649 | $this->scale->tableTitle->GetWidth($this->img) |
||
650 | ), |
||
651 | $this->scale->actinfo->GetWidth($this->img) |
||
652 | ); |
||
653 | |||
654 | // Add the width of the vertivcal divider line |
||
655 | $titlewidth += $this->scale->divider->iWeight * 2; |
||
656 | |||
657 | // Adjust the width by the user specified zoom factor |
||
658 | $fw *= $this->iZoomFactor; |
||
659 | |||
660 | // Now get the total width taking |
||
661 | // titlewidth, left and rigt margin, dayfont size |
||
662 | // into account |
||
663 | $width = $titlewidth + $nd * $fw + $lm + $rm; |
||
664 | } else { |
||
665 | $width = $this->img->width; |
||
666 | } |
||
667 | |||
668 | $width = round($width); |
||
669 | $height = round($height); |
||
670 | // Make a sanity check on image size |
||
671 | if ($width > MAX_GANTTIMG_SIZE_W || $height > MAX_GANTTIMG_SIZE_H) { |
||
672 | Util\JpGraphError::RaiseL(6007, $width, $height); |
||
673 | //("Sanity check for automatic Gantt chart size failed. Either the width (=$width) or height (=$height) is larger than MAX_GANTTIMG_SIZE. This could potentially be caused by a wrong date in one of the activities."); |
||
674 | } |
||
675 | $this->img->CreateImgCanvas($width, $height); |
||
676 | $this->img->SetMargin($lm, $rm, $tm, $bm); |
||
677 | } |
||
678 | } |
||
679 | |||
680 | // Return an array width the maximum width for each activity |
||
681 | // column. This is used when we autosize the columns where we need |
||
682 | // to find out the maximum width of each column. In order to do that we |
||
683 | // must walk through all the objects, sigh... |
||
684 | public function GetMaxActInfoColWidth() |
||
685 | { |
||
686 | $n = safe_count($this->iObj); |
||
687 | if ($n == 0) { |
||
688 | return; |
||
689 | } |
||
690 | |||
691 | $w = []; |
||
692 | $m = $this->scale->actinfo->iLeftColMargin + $this->scale->actinfo->iRightColMargin; |
||
693 | |||
694 | for ($i = 0; $i < $n; ++$i) { |
||
695 | $tmp = $this->iObj[$i]->title->GetColWidth($this->img, $m); |
||
696 | $nn = safe_count($tmp); |
||
697 | for ($j = 0; $j < $nn; ++$j) { |
||
698 | if (empty($w[$j])) { |
||
699 | $w[$j] = $tmp[$j]; |
||
700 | } else { |
||
701 | $w[$j] = max($w[$j], $tmp[$j]); |
||
702 | } |
||
703 | } |
||
704 | } |
||
705 | |||
706 | return $w; |
||
707 | } |
||
708 | |||
709 | // Stroke the gantt chart |
||
710 | public function Stroke($aStrokeFileName = '') |
||
711 | { |
||
712 | // If the filename is the predefined value = '_csim_special_' |
||
713 | // we assume that the call to stroke only needs to do enough |
||
714 | // to correctly generate the CSIM maps. |
||
715 | // We use this variable to skip things we don't strictly need |
||
716 | // to do to generate the image map to improve performance |
||
717 | // a best we can. Therefor you will see a lot of tests !$_csim in the |
||
718 | // code below. |
||
719 | $_csim = ($aStrokeFileName === _CSIM_SPECIALFILE); |
||
720 | |||
721 | // Should we autoscale dates? |
||
722 | |||
723 | if (!$this->scale->IsRangeSet()) { |
||
724 | list($min, $max) = $this->GetBarMinMax(); |
||
725 | $this->scale->SetRange($min, $max); |
||
726 | } |
||
727 | |||
728 | $this->scale->AdjustStartEndDay(); |
||
729 | |||
730 | // Check if we should autoscale the image |
||
731 | $this->AutoSize(); |
||
732 | |||
733 | // Should we start from the top or just spread the bars out even over the |
||
734 | // available height |
||
735 | $this->scale->SetVertLayout($this->iLayout); |
||
736 | if ($this->iLayout == GANTT_FROMTOP) { |
||
737 | $maxheight = max($this->GetMaxLabelHeight(), $this->GetMaxBarAbsHeight()); |
||
738 | $this->scale->SetVertSpacing($maxheight * (1 + $this->iLabelVMarginFactor)); |
||
739 | } |
||
740 | // If it hasn't been set find out the maximum line number |
||
741 | if ($this->scale->iVertLines == -1) { |
||
742 | $this->scale->iVertLines = $this->GetBarMaxLineNumber() + 1; |
||
743 | } |
||
744 | |||
745 | $maxwidth = max( |
||
746 | $this->scale->actinfo->GetWidth($this->img), |
||
747 | max( |
||
748 | $this->GetMaxLabelWidth(), |
||
749 | $this->scale->tableTitle->GetWidth($this->img) |
||
750 | ) |
||
751 | ); |
||
752 | |||
753 | $this->scale->SetLabelWidth($maxwidth + $this->scale->divider->iWeight); //*(1+$this->iLabelHMarginFactor)); |
||
754 | |||
755 | if (!$_csim) { |
||
756 | $this->StrokePlotArea(); |
||
757 | if ($this->iIconDepth == DEPTH_BACK) { |
||
758 | $this->StrokeIcons(); |
||
759 | } |
||
760 | } |
||
761 | |||
762 | $this->scale->Stroke(); |
||
763 | |||
764 | if (!$_csim) { |
||
765 | // Due to a minor off by 1 bug we need to temporarily adjust the margin |
||
766 | --$this->img->right_margin; |
||
767 | $this->StrokePlotBox(); |
||
768 | ++$this->img->right_margin; |
||
769 | } |
||
770 | |||
771 | // Stroke Grid line |
||
772 | $this->hgrid->Stroke($this->img, $this->scale); |
||
773 | |||
774 | $n = safe_count($this->iObj); |
||
775 | for ($i = 0; $i < $n; ++$i) { |
||
776 | //$this->iObj[$i]->SetLabelLeftMargin(round($maxwidth*$this->iLabelHMarginFactor/2)); |
||
777 | $this->iObj[$i]->Stroke($this->img, $this->scale); |
||
778 | } |
||
779 | |||
780 | $this->StrokeTitles(); |
||
781 | |||
782 | if (!$_csim) { |
||
783 | $this->StrokeConstrains(); |
||
784 | $this->footer->Stroke($this->img); |
||
785 | |||
786 | if ($this->iIconDepth == DEPTH_FRONT) { |
||
787 | $this->StrokeIcons(); |
||
788 | } |
||
789 | |||
790 | // Stroke all added user texts |
||
791 | $this->StrokeTexts(); |
||
792 | |||
793 | // Should we do any final image transformation |
||
794 | if ($this->iImgTrans) { |
||
795 | $tform = new Image\ImgTrans($this->img->img); |
||
796 | $this->img->img = $tform->Skew3D( |
||
797 | $this->iImgTransHorizon, |
||
798 | $this->iImgTransSkewDist, |
||
799 | $this->iImgTransDirection, |
||
800 | $this->iImgTransHighQ, |
||
801 | $this->iImgTransMinSize, |
||
802 | $this->iImgTransFillColor, |
||
803 | $this->iImgTransBorder |
||
804 | ); |
||
805 | } |
||
806 | |||
807 | // If the filename is given as the special "__handle" |
||
808 | // then the image handler is returned and the image is NOT |
||
809 | // streamed back |
||
810 | if ($aStrokeFileName == _IMG_HANDLER) { |
||
811 | return $this->img->img; |
||
812 | } |
||
813 | // Finally stream the generated picture |
||
814 | $this->cache->PutAndStream( |
||
815 | $this->img, |
||
816 | $this->cache_name, |
||
817 | $this->inline, |
||
818 | $aStrokeFileName |
||
819 | ); |
||
820 | } |
||
821 | } |
||
822 | |||
823 | public function StrokeConstrains() |
||
824 | { |
||
825 | $n = safe_count($this->iObj); |
||
826 | |||
827 | // Stroke all constrains |
||
828 | for ($i = 0; $i < $n; ++$i) { |
||
829 | // Some gantt objects may not have constraints associated with them |
||
830 | // for example we can add Plot\IconPlots which doesn't have this property. |
||
831 | if (empty($this->iObj[$i]->constraints)) { |
||
832 | continue; |
||
833 | } |
||
834 | |||
835 | $numConstrains = safe_count($this->iObj[$i]->constraints); |
||
836 | |||
837 | for ($k = 0; $k < $numConstrains; ++$k) { |
||
838 | $vpos = $this->iObj[$i]->constraints[$k]->iConstrainRow; |
||
839 | if ($vpos >= 0) { |
||
840 | $c1 = $this->iObj[$i]->iConstrainPos; |
||
841 | |||
842 | // Find out which object is on the target row |
||
843 | $targetobj = -1; |
||
844 | for ($j = 0; $j < $n && $targetobj == -1; ++$j) { |
||
845 | if ($this->iObj[$j]->iVPos == $vpos) { |
||
846 | $targetobj = $j; |
||
847 | } |
||
848 | } |
||
849 | if ($targetobj == -1) { |
||
850 | Util\JpGraphError::RaiseL(6008, $this->iObj[$i]->iVPos, $vpos); |
||
851 | //('You have specifed a constrain from row='.$this->iObj[$i]->iVPos.' to row='.$vpos.' which does not have any activity.'); |
||
852 | } |
||
853 | $c2 = $this->iObj[$targetobj]->iConstrainPos; |
||
854 | if (safe_count($c1) == 4 && safe_count($c2) == 4) { |
||
855 | switch ($this->iObj[$i]->constraints[$k]->iConstrainType) { |
||
856 | case CONSTRAIN_ENDSTART: |
||
857 | if ($c1[1] < $c2[1]) { |
||
858 | $link = new Image\GanttLink($c1[2], $c1[3], $c2[0], $c2[1]); |
||
859 | } else { |
||
860 | $link = new Image\GanttLink($c1[2], $c1[1], $c2[0], $c2[3]); |
||
861 | } |
||
862 | $link->SetPath(3); |
||
863 | |||
864 | break; |
||
865 | case CONSTRAIN_STARTEND: |
||
866 | if ($c1[1] < $c2[1]) { |
||
867 | $link = new Image\GanttLink($c1[0], $c1[3], $c2[2], $c2[1]); |
||
868 | } else { |
||
869 | $link = new Image\GanttLink($c1[0], $c1[1], $c2[2], $c2[3]); |
||
870 | } |
||
871 | $link->SetPath(0); |
||
872 | |||
873 | break; |
||
874 | case CONSTRAIN_ENDEND: |
||
875 | if ($c1[1] < $c2[1]) { |
||
876 | $link = new Image\GanttLink($c1[2], $c1[3], $c2[2], $c2[1]); |
||
877 | } else { |
||
878 | $link = new Image\GanttLink($c1[2], $c1[1], $c2[2], $c2[3]); |
||
879 | } |
||
880 | $link->SetPath(1); |
||
881 | |||
882 | break; |
||
883 | case CONSTRAIN_STARTSTART: |
||
884 | if ($c1[1] < $c2[1]) { |
||
885 | $link = new Image\GanttLink($c1[0], $c1[3], $c2[0], $c2[1]); |
||
886 | } else { |
||
887 | $link = new Image\GanttLink($c1[0], $c1[1], $c2[0], $c2[3]); |
||
888 | } |
||
889 | $link->SetPath(3); |
||
890 | |||
891 | break; |
||
892 | default: |
||
893 | Util\JpGraphError::RaiseL(6009, $this->iObj[$i]->iVPos, $vpos); |
||
894 | //('Unknown constrain type specified from row='.$this->iObj[$i]->iVPos.' to row='.$vpos); |
||
895 | break; |
||
896 | } |
||
897 | |||
898 | $link->SetColor($this->iObj[$i]->constraints[$k]->iConstrainColor); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
899 | $link->SetArrow( |
||
900 | $this->iObj[$i]->constraints[$k]->iConstrainArrowSize, |
||
901 | $this->iObj[$i]->constraints[$k]->iConstrainArrowType |
||
902 | ); |
||
903 | |||
904 | $link->Stroke($this->img); |
||
905 | } |
||
906 | } |
||
907 | } |
||
908 | } |
||
909 | } |
||
910 | |||
911 | public function GetCSIMAreas() |
||
912 | { |
||
913 | if (!$this->iHasStroked) { |
||
914 | $this->Stroke(_CSIM_SPECIALFILE); |
||
915 | } |
||
916 | |||
917 | $csim = $this->title->GetCSIMAreas(); |
||
918 | $csim .= $this->subtitle->GetCSIMAreas(); |
||
919 | $csim .= $this->subsubtitle->GetCSIMAreas(); |
||
920 | |||
921 | $n = safe_count($this->iObj); |
||
922 | for ($i = $n - 1; $i >= 0; --$i) { |
||
923 | $csim .= $this->iObj[$i]->GetCSIMArea(); |
||
924 | } |
||
925 | |||
926 | return $csim; |
||
927 | } |
||
928 | } |
||
929 |