HuasoFoundries /
jpgraph
| 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
Loading history...
|
|||
| 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 |