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 |
|
|
|
|
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'); |
|
|
|
|
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; |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|