src/plot/GanttBar.php
<?php
/**
* JPGraph v4.0.3
*/
namespace Amenadiel\JpGraph\Plot;
use Amenadiel\JpGraph\Graph;
use Amenadiel\JpGraph\Image;
use Amenadiel\JpGraph\Text;
use Amenadiel\JpGraph\Util;
/**
* @class GanttBar
* // Responsible for formatting individual gantt bars
*/
class GanttBar extends GanttPlotObject
{
public $progress;
public $leftMark;
public $rightMark;
private $iEnd;
private $iHeightFactor = 0.5;
private $iFillColor = 'white';
private $iFrameColor = 'black';
private $iShadow = false;
private $iShadowColor = 'darkgray';
private $iShadowWidth = 1;
private $iShadowFrame = 'black';
private $iPattern = GANTT_RDIAG;
private $iPatternColor = 'blue';
private $iPatternDensity = 95;
private $iBreakStyle = false;
private $iBreakLineStyle = 'dotted';
private $iBreakLineWeight = 1;
/**
* CONSTRUCTOR.
*
* @param mixed $aPos
* @param mixed $aLabel
* @param mixed $aStart
* @param mixed $aEnd
* @param mixed $aCaption
* @param mixed $aHeightFactor
*/
public function __construct($aPos, $aLabel, $aStart, $aEnd, $aCaption = '', $aHeightFactor = 0.6)
{
parent::__construct();
$this->iStart = $aStart;
// Is the end date given as a date or as number of days added to start date?
if (is_string($aEnd)) {
// If end date has been specified without a time we will asssume
// end date is at the end of that date
if (strpos($aEnd, ':') === false) {
$this->iEnd = strtotime($aEnd) + SECPERDAY - 1;
} else {
$this->iEnd = $aEnd;
}
} elseif (is_int($aEnd) || is_float($aEnd)) {
$this->iEnd = strtotime($aStart) + round($aEnd * SECPERDAY);
}
$this->iVPos = $aPos;
$this->iHeightFactor = $aHeightFactor;
$this->title->Set($aLabel);
$this->caption = new Text\TextProperty($aCaption);
$this->caption->Align('left', 'center');
$this->leftMark = new PlotMark();
$this->leftMark->Hide();
$this->rightMark = new PlotMark();
$this->rightMark->Hide();
$this->progress = new Image\Progress();
}
/**
* PUBLIC METHODS.
*
* @param mixed $aShadow
* @param mixed $aColor
*/
public function SetShadow($aShadow = true, $aColor = 'gray')
{
$this->iShadow = $aShadow;
$this->iShadowColor = $aColor;
}
public function SetBreakStyle($aFlg = true, $aLineStyle = 'dotted', $aLineWeight = 1)
{
$this->iBreakStyle = $aFlg;
$this->iBreakLineStyle = $aLineStyle;
$this->iBreakLineWeight = $aLineWeight;
}
public function GetMaxDate()
{
return $this->iEnd;
}
public function SetHeight($aHeight)
{
$this->iHeightFactor = $aHeight;
}
public function SetColor($aColor)
{
$this->iFrameColor = $aColor;
}
public function SetFillColor($aColor)
{
$this->iFillColor = $aColor;
}
public function GetAbsHeight($aImg)
{
if (is_int($this->iHeightFactor) || $this->leftMark->show || $this->rightMark->show) {
$m = -1;
if (is_int($this->iHeightFactor)) {
$m = $this->iHeightFactor;
}
if ($this->leftMark->show) {
$m = max($m, $this->leftMark->width * 2);
}
if ($this->rightMark->show) {
$m = max($m, $this->rightMark->width * 2);
}
return $m;
}
return -1;
}
public function SetPattern($aPattern, $aColor = 'blue', $aDensity = 95)
{
$this->iPattern = $aPattern;
$this->iPatternColor = $aColor;
$this->iPatternDensity = $aDensity;
}
public function Stroke($aImg, $aScale)
{
$factory = new Graph\RectPatternFactory();
$prect = $factory->Create($this->iPattern, $this->iPatternColor);
$prect->SetDensity($this->iPatternDensity);
// If height factor is specified as a float between 0,1 then we take it as meaning
// percetage of the scale width between horizontal line.
// If it is an integer > 1 we take it to mean the absolute height in pixels
if ($this->iHeightFactor > -0.0 && $this->iHeightFactor <= 1.1) {
$vs = $aScale->GetVertSpacing() * $this->iHeightFactor;
} elseif (is_int($this->iHeightFactor) && $this->iHeightFactor > 2 && $this->iHeightFactor < 200) {
$vs = $this->iHeightFactor;
} else {
Util\JpGraphError::RaiseL(6028, $this->iHeightFactor);
// ("Specified height (".$this->iHeightFactor.") for gantt bar is out of range.");
}
// Clip date to min max dates to show
$st = $aScale->NormalizeDate($this->iStart);
$en = $aScale->NormalizeDate($this->iEnd);
$limst = max($st, $aScale->iStartDate);
$limen = min($en, $aScale->iEndDate);
$xt = round($aScale->TranslateDate($limst));
$xb = round($aScale->TranslateDate($limen));
$yt = round($aScale->TranslateVertPos($this->iVPos) - $vs - ($aScale->GetVertSpacing() / 2 - $vs / 2));
$yb = round($aScale->TranslateVertPos($this->iVPos) - ($aScale->GetVertSpacing() / 2 - $vs / 2));
$middle = round($yt + ($yb - $yt) / 2);
$this->StrokeActInfo($aImg, $aScale, $middle);
// CSIM for title
if (!empty($this->title->csimtarget)) {
$colwidth = $this->title->GetColWidth($aImg);
$colstarts = [];
$aScale->actinfo->GetColStart($aImg, $colstarts, true);
$n = min(safe_count($colwidth), safe_count($this->title->csimtarget));
for ($i = 0; $i < $n; ++$i) {
$title_xt = $colstarts[$i];
$title_xb = $title_xt + $colwidth[$i];
$coords = "${title_xt},${yt},${title_xb},${yt},${title_xb},${yb},${title_xt},${yb}";
if (!empty($this->title->csimtarget[$i])) {
$this->csimarea .= "<area shape=\"poly\" coords=\"${coords}\" href=\"" . $this->title->csimtarget[$i] . '"';
if (!empty($this->title->csimwintarget[$i])) {
$this->csimarea .= 'target="' . $this->title->csimwintarget[$i] . '" ';
}
if (!empty($this->title->csimalt[$i])) {
$tmp = $this->title->csimalt[$i];
$this->csimarea .= " title=\"${tmp}\" alt=\"${tmp}\" ";
}
$this->csimarea .= " />\n";
}
}
}
// Check if the bar is totally outside the current scale range
if ($en < $aScale->iStartDate || $st > $aScale->iEndDate) {
return;
}
// Remember the positions for the bar
$this->SetConstrainPos($xt, $yt, $xb, $yb);
$prect->ShowFrame(false);
$prect->SetBackground($this->iFillColor);
if ($this->iBreakStyle) {
$aImg->SetColor($this->iFrameColor);
$olds = $aImg->SetLineStyle($this->iBreakLineStyle);
$oldw = $aImg->SetLineWeight($this->iBreakLineWeight);
$aImg->StyleLine($xt, $yt, $xb, $yt);
$aImg->StyleLine($xt, $yb, $xb, $yb);
$aImg->SetLineStyle($olds);
$aImg->SetLineWeight($oldw);
} else {
if ($this->iShadow) {
$aImg->SetColor($this->iFrameColor);
$aImg->ShadowRectangle($xt, $yt, $xb, $yb, $this->iFillColor, $this->iShadowWidth, $this->iShadowColor);
$prect->SetPos(new Util\Rectangle($xt + 1, $yt + 1, $xb - $xt - $this->iShadowWidth - 2, $yb - $yt - $this->iShadowWidth - 2));
$prect->Stroke($aImg);
} else {
$prect->SetPos(new Util\Rectangle($xt, $yt, $xb - $xt + 1, $yb - $yt + 1));
$prect->Stroke($aImg);
$aImg->SetColor($this->iFrameColor);
$aImg->Rectangle($xt, $yt, $xb, $yb);
}
}
// CSIM for bar
if (!empty($this->csimtarget)) {
$coords = "${xt},${yt},${xb},${yt},${xb},${yb},${xt},${yb}";
$this->csimarea .= "<area shape=\"poly\" coords=\"${coords}\" href=\"" . $this->csimtarget . '"';
if (!empty($this->csimwintarget)) {
$this->csimarea .= ' target="' . $this->csimwintarget . '" ';
}
if ($this->csimalt != '') {
$tmp = $this->csimalt;
$this->csimarea .= " title=\"${tmp}\" alt=\"${tmp}\" ";
}
$this->csimarea .= " />\n";
}
// Draw progress bar inside activity bar
if ($this->progress->iProgress > 0) {
$xtp = $aScale->TranslateDate($st);
$xbp = $aScale->TranslateDate($en);
$len = ($xbp - $xtp) * $this->progress->iProgress;
$endpos = $xtp + $len;
if ($endpos > $xt) {
// Take away the length of the progress that is not visible (before the start date)
$len -= ($xt - $xtp);
// Is the the progress bar visible after the start date?
if ($xtp < $xt) {
$xtp = $xt;
}
// Make sure that the progess bar doesn't extend over the end date
if ($xtp + $len - 1 > $xb) {
$len = $xb - $xtp;
}
$prog = $factory->Create($this->progress->iPattern, $this->progress->iColor);
$prog->SetDensity($this->progress->iDensity);
$prog->SetBackground($this->progress->iFillColor);
$barheight = ($yb - $yt + 1);
if ($this->iShadow) {
$barheight -= $this->iShadowWidth;
}
$progressheight = floor($barheight * $this->progress->iHeight);
$marg = ceil(($barheight - $progressheight) / 2);
$pos = new Util\Rectangle($xtp, $yt + $marg, $len, $barheight - 2 * $marg);
$prog->SetPos($pos);
$prog->Stroke($aImg);
}
}
// We don't plot the end mark if the bar has been capped
if ($limst == $st) {
$y = $middle;
// We treat the RIGHT and LEFT triangle mark a little bi
// special so that these marks are placed right under the
// bar.
if ($this->leftMark->GetType() == MARK_LEFTTRIANGLE) {
$y = $yb;
}
$this->leftMark->Stroke($aImg, $xt, $y);
}
if ($limen == $en) {
$y = $middle;
// We treat the RIGHT and LEFT triangle mark a little bi
// special so that these marks are placed right under the
// bar.
if ($this->rightMark->GetType() == MARK_RIGHTTRIANGLE) {
$y = $yb;
}
$this->rightMark->Stroke($aImg, $xb, $y);
$margin = $this->iCaptionMargin;
if ($this->rightMark->show) {
$margin += $this->rightMark->GetWidth();
}
$this->caption->Stroke($aImg, $xb + $margin, $middle);
}
}
}