mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
Replacement of the impact Flash based analysis graph by graphviz + Raphael + TCPDF. ALPHA version.
SVN:trunk[3554]
This commit is contained in:
@@ -1412,13 +1412,20 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
}
|
||||
// Relations...
|
||||
$aRelations = MetaModel::EnumRelations($sClass);
|
||||
$aRelations = MetaModel::EnumRelationsEx($sClass);
|
||||
if (count($aRelations))
|
||||
{
|
||||
$this->AddMenuSeparator($aActions);
|
||||
foreach($aRelations as $sRelationCode)
|
||||
foreach($aRelations as $sRelationCode => $aRelationInfo)
|
||||
{
|
||||
$aActions[$sRelationCode] = array ('label' => MetaModel::GetRelationLabel($sRelationCode), 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id{$sContext}");
|
||||
if (array_key_exists('down', $aRelationInfo))
|
||||
{
|
||||
$aActions[$sRelationCode.'_down'] = array ('label' => $aRelationInfo['down'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}");
|
||||
}
|
||||
if (array_key_exists('up', $aRelationInfo))
|
||||
{
|
||||
$aActions[$sRelationCode.'_up'] = array ('label' => $aRelationInfo['up'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
938
core/displayablegraph.class.inc.php
Normal file
938
core/displayablegraph.class.inc.php
Normal file
@@ -0,0 +1,938 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Special kind of Graph for producing some nice output
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class DisplayableNode extends GraphNode
|
||||
{
|
||||
public $x;
|
||||
public $y;
|
||||
|
||||
/**
|
||||
* Create a new node inside a graph
|
||||
* @param SimpleGraph $oGraph
|
||||
* @param string $sId The unique identifier of this node inside the graph
|
||||
* @param number $x Horizontal position
|
||||
* @param number $y Vertical position
|
||||
*/
|
||||
public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
|
||||
{
|
||||
parent::__construct($oGraph, $sId);
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
$this->bFiltered = false;
|
||||
}
|
||||
|
||||
public function GetIconURL()
|
||||
{
|
||||
return $this->GetProperty('icon_url', '');
|
||||
}
|
||||
|
||||
public function GetLabel()
|
||||
{
|
||||
return $this->GetProperty('label', $this->sId);
|
||||
}
|
||||
|
||||
public function GetWidth()
|
||||
{
|
||||
return max(32, 5*strlen($this->GetProperty('label'))); // approximation of the text's bounding box
|
||||
}
|
||||
|
||||
public function GetHeight()
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
public function Distance2(DisplayableNode $oNode)
|
||||
{
|
||||
$dx = $this->x - $oNode->x;
|
||||
$dy = $this->y - $oNode->y;
|
||||
|
||||
$d2 = $dx*$dx + $dy*$dy - $this->GetHeight()*$this->GetHeight();
|
||||
if ($d2 < 40)
|
||||
{
|
||||
$d2 = 40;
|
||||
}
|
||||
return $d2;
|
||||
}
|
||||
|
||||
public function Distance(DisplayableNode $oNode)
|
||||
{
|
||||
return sqrt($this->Distance2($oNode));
|
||||
}
|
||||
|
||||
public function GetForRaphael()
|
||||
{
|
||||
$aNode = array();
|
||||
$aNode['shape'] = 'icon';
|
||||
$aNode['icon_url'] = $this->GetIconURL();
|
||||
$aNode['width'] = 32;
|
||||
$aNode['source'] = ($this->GetProperty('source') == true);
|
||||
$aNode['sink'] = ($this->GetProperty('sink') == true);
|
||||
$aNode['x'] = $this->x;
|
||||
$aNode['y']= $this->y;
|
||||
$aNode['label'] = $this->GetLabel();
|
||||
$aNode['id'] = $this->GetId();
|
||||
$fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
|
||||
$aNode['icon_attr'] = array('opacity' => $fOpacity);
|
||||
$aNode['text_attr'] = array('opacity' => $fOpacity);
|
||||
return $aNode;
|
||||
}
|
||||
|
||||
public function RenderAsPDF(TCPDF $oPdf, $fScale)
|
||||
{
|
||||
$Alpha = 1.0;
|
||||
$oPdf->SetFillColor(200, 200, 200);
|
||||
$oPdf->setAlpha(1);
|
||||
|
||||
$sIconUrl = $this->GetProperty('icon_url');
|
||||
$sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
|
||||
|
||||
if ($this->GetProperty('source'))
|
||||
{
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
|
||||
}
|
||||
else if ($this->GetProperty('sink'))
|
||||
{
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
|
||||
}
|
||||
|
||||
if (!$this->GetProperty('is_reached'))
|
||||
{
|
||||
if (function_exists('imagecreatefrompng'))
|
||||
{
|
||||
$im = imagecreatefrompng($sIconPath);
|
||||
|
||||
if($im && imagefilter($im, IMG_FILTER_COLORIZE, 255, 255, 255))
|
||||
{
|
||||
$sTempImageName = APPROOT.'data/tmp-'.basename($sIconPath);
|
||||
imagesavealpha($im, true);
|
||||
imagepng($im, $sTempImageName);
|
||||
imagedestroy($im);
|
||||
$oPdf->Image($sTempImageName, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
|
||||
}
|
||||
}
|
||||
$Alpha = 0.4;
|
||||
$oPdf->setAlpha($Alpha);
|
||||
}
|
||||
|
||||
$oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
|
||||
//$oPdf->Image(APPROOT.'images/blank-100x100.png', ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale, '', '', '', false, 300, '', false, $mask);
|
||||
//Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array())
|
||||
|
||||
$oPdf->SetFont('Helvetica', '', 24 * $fScale, '', true);
|
||||
$width = $oPdf->GetStringWidth($this->GetProperty('label'));
|
||||
$height = $oPdf->GetStringHeight(1000, $this->GetProperty('label'));
|
||||
$oPdf->setAlpha(0.6 * $Alpha);
|
||||
$oPdf->SetFillColor(255, 255, 255);
|
||||
$oPdf->SetDrawColor(255, 255, 255);
|
||||
$oPdf->Rect($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $width, $height, 'DF');
|
||||
$oPdf->setAlpha($Alpha);
|
||||
$oPdf->SetTextColor(0, 0, 0);
|
||||
$oPdf->Text($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $this->GetProperty('label'));
|
||||
}
|
||||
|
||||
public function GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
|
||||
{
|
||||
//echo "<p>".$this->GetProperty('label').":</p>";
|
||||
|
||||
if ($this->GetProperty('grouped') === true) return;
|
||||
$this->SetProperty('grouped', true);
|
||||
|
||||
if ($bDirectionDown)
|
||||
{
|
||||
$aNodesPerClass = array();
|
||||
foreach($this->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$oNode = $oEdge->GetSinkNode();
|
||||
|
||||
if ($oNode->GetProperty('class') !== null)
|
||||
{
|
||||
$sClass = $oNode->GetProperty('class');
|
||||
if (($sClass!== null) && (!array_key_exists($sClass, $aNodesPerClass)))
|
||||
{
|
||||
$aNodesPerClass[$sClass] = array(
|
||||
'reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
),
|
||||
'not_reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
)
|
||||
);
|
||||
}
|
||||
$sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
|
||||
if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
|
||||
{
|
||||
$aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
|
||||
$aNodesPerClass[$sClass][$sKey]['count'] += (int)$oNode->GetProperty('count', 1);
|
||||
//echo "<p>New count: ".$aNodesPerClass[$sClass][$sKey]['count']."</p>";
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($aNodesPerClass as $sClass => $aDefs)
|
||||
{
|
||||
foreach($aDefs as $sStatus => $aGroupProps)
|
||||
{
|
||||
//echo "<p>$sClass/$sStatus: {$aGroupProps['count']} object(s), actually: ".count($aGroupProps['nodes'])."</p>";
|
||||
if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.$sClass);
|
||||
$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
|
||||
$oNewNode->SetProperty('count', $aGroupProps['count']);
|
||||
//$oNewNode->SetProperty('grouped', true);
|
||||
|
||||
$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
|
||||
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
}
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$aOutgoing[] = $oEdge->GetSinkNode();
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
if ($oGraph->GetNode($oNode->GetId()))
|
||||
{
|
||||
$oGraph->_RemoveNode($oNode);
|
||||
}
|
||||
}
|
||||
$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayableRedundancyNode extends DisplayableNode
|
||||
{
|
||||
public function GetWidth()
|
||||
{
|
||||
return 24;
|
||||
}
|
||||
|
||||
public function GetForRaphael()
|
||||
{
|
||||
$aNode = array();
|
||||
$aNode['shape'] = 'disc';
|
||||
$aNode['icon_url'] = $this->GetIconURL();
|
||||
$aNode['source'] = ($this->GetProperty('source') == true);
|
||||
$aNode['width'] = $this->GetWidth();
|
||||
$aNode['x'] = $this->x;
|
||||
$aNode['y']= $this->y;
|
||||
$aNode['label'] = $this->GetLabel();
|
||||
$aNode['id'] = $this->GetId();
|
||||
$fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
|
||||
$aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#c33', 'opacity' => $fDiscOpacity);
|
||||
$fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
|
||||
$aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity);
|
||||
return $aNode;
|
||||
}
|
||||
|
||||
public function RenderAsPDF(TCPDF $oPdf, $fScale)
|
||||
{
|
||||
$oPdf->SetAlpha(1);
|
||||
$oPdf->SetFillColor(200, 0, 0);
|
||||
$oPdf->SetDrawColor(0, 0, 0);
|
||||
$oPdf->Circle($this->x*$fScale, $this->y*$fScale, 16*$fScale, 0, 360, 'DF');
|
||||
|
||||
$oPdf->SetTextColor(255, 255, 255);
|
||||
$oPdf->SetFont('Helvetica', '', 28 * $fScale, '', true);
|
||||
$sLabel = (string)$this->GetProperty('label');
|
||||
$width = $oPdf->GetStringWidth($sLabel, 'Helvetica', 'B', 24*$fScale);
|
||||
$height = $oPdf->GetStringHeight(1000, $sLabel);
|
||||
$xPos = (float)$this->x*$fScale - $width/2;
|
||||
$yPos = (float)$this->y*$fScale - $height/2;
|
||||
// $oPdf->Rect($xPos, $yPos, $width, $height, 'D');
|
||||
// $oPdf->Text($xPos, $yPos, $sLabel);
|
||||
|
||||
$oPdf->SetXY(($this->x - 16)*$fScale, ($this->y - 16)*$fScale);
|
||||
|
||||
// text on center
|
||||
$oPdf->Cell(32*$fScale, 32*$fScale, $sLabel, 0, 0, 'C', 0, '', 0, false, 'T', 'C');
|
||||
}
|
||||
|
||||
public function GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
|
||||
{
|
||||
parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
|
||||
if ($bDirectionUp)
|
||||
{
|
||||
$aNodesPerClass = array();
|
||||
foreach($this->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$oNode = $oEdge->GetSourceNode();
|
||||
|
||||
if (($oNode->GetProperty('class') !== null) && (!$oNode->GetProperty('is_reached')))
|
||||
{
|
||||
$sClass = $oNode->GetProperty('class');
|
||||
if (!array_key_exists($sClass, $aNodesPerClass))
|
||||
{
|
||||
$aNodesPerClass[$sClass] = array('reached' => array(), 'not_reached' => array());
|
||||
}
|
||||
$aNodesPerClass[$sClass][$oNode->GetProperty('is_reached') ? 'reached' : 'not_reached'][] = $oNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
//$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($aNodesPerClass as $sClass => $aDefs)
|
||||
{
|
||||
foreach($aDefs as $sStatus => $aNodes)
|
||||
{
|
||||
//echo "<p>".$this->GetId().' has '.count($aNodes)." neighbours of class $sClass in status $sStatus\n";
|
||||
if (count($aNodes) >= $iThresholdCount)
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
|
||||
$oNewNode->SetProperty('label', 'x'.count($aNodes));
|
||||
$oNewNode->SetProperty('icon_url', $aNodes[0]->GetProperty('icon_url'));
|
||||
$oNewNode->SetProperty('is_reached', $aNodes[0]->GetProperty('is_reached'));
|
||||
|
||||
$oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
|
||||
|
||||
foreach($aNodes as $oNode)
|
||||
{
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
$aOutgoing[] = $oEdge->GetSinkNode();
|
||||
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
}
|
||||
//echo "<p>Replacing ".$oNode->GetId().' by '.$oNewNode->GetId()."\n";
|
||||
$oGraph->_RemoveNode($oNode);
|
||||
}
|
||||
//$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aNodes as $oNode)
|
||||
{
|
||||
//$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayableEdge extends GraphEdge
|
||||
{
|
||||
public function RenderAsPDF(TCPDF $oPdf, $fScale)
|
||||
{
|
||||
$xStart = $this->GetSourceNode()->x * $fScale;
|
||||
$yStart = $this->GetSourceNode()->y * $fScale;
|
||||
$xEnd = $this->GetSinkNode()->x * $fScale;
|
||||
$yEnd = $this->GetSinkNode()->y * $fScale;
|
||||
|
||||
$bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached'));
|
||||
|
||||
$oPdf->setAlpha(1);
|
||||
if ($bReached)
|
||||
{
|
||||
$aColor = array(100, 100, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aColor = array(200, 200, 200);
|
||||
}
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aColor));
|
||||
$oPdf->Line($xStart, $yStart, $xEnd, $yEnd);
|
||||
|
||||
|
||||
$vx = $xEnd - $xStart;
|
||||
$vy = $yEnd - $yStart;
|
||||
$l = sqrt($vx*$vx + $vy*$vy);
|
||||
$vx = $vx / $l;
|
||||
$vy = $vy / $l;
|
||||
$ux = -$vy;
|
||||
$uy = $vx;
|
||||
$lPos = max($l/2, $l - 40*$fScale);
|
||||
$iArrowSize = 5*$fScale;
|
||||
|
||||
$x = $xStart + $lPos * $vx;
|
||||
$y = $yStart + $lPos * $vy;
|
||||
$oPdf->Line($x, $y, $x + $iArrowSize * ($ux-$vx), $y + $iArrowSize * ($uy-$vy));
|
||||
$oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy));
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayableGroupNode extends DisplayableNode
|
||||
{
|
||||
public function GetWidth()
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
public function GetForRaphael()
|
||||
{
|
||||
$aNode = array();
|
||||
$aNode['shape'] = 'group';
|
||||
$aNode['icon_url'] = $this->GetIconURL();
|
||||
$aNode['source'] = ($this->GetProperty('source') == true);
|
||||
$aNode['width'] = $this->GetWidth();
|
||||
$aNode['x'] = $this->x;
|
||||
$aNode['y']= $this->y;
|
||||
$aNode['label'] = $this->GetLabel();
|
||||
$aNode['id'] = $this->GetId();
|
||||
$fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
|
||||
$fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
|
||||
$aNode['icon_attr'] = array('opacity' => $fTextOpacity);
|
||||
$aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#fff', 'opacity' => $fDiscOpacity);
|
||||
$aNode['text_attr'] = array('fill' => '#000', 'opacity' => $fTextOpacity);
|
||||
return $aNode;
|
||||
}
|
||||
|
||||
public function RenderAsPDF(TCPDF $oPdf, $fScale)
|
||||
{
|
||||
$bReached = $this->GetProperty('is_reached');
|
||||
$oPdf->SetFillColor(255, 255, 255);
|
||||
if ($bReached)
|
||||
{
|
||||
$aBorderColor = array(100, 100, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aBorderColor = array(200, 200, 200);
|
||||
}
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
|
||||
|
||||
$sIconUrl = $this->GetProperty('icon_url');
|
||||
$sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
|
||||
$oPdf->SetAlpha(1);
|
||||
$oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
|
||||
|
||||
if ($bReached)
|
||||
{
|
||||
$oPdf->SetAlpha(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPdf->SetAlpha(0.4);
|
||||
}
|
||||
$oPdf->Image($sIconPath, ($this->x - 17)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->Image($sIconPath, ($this->x + 1)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->Image($sIconPath, ($this->x -8)*$fScale, ($this->y +1)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->SetFont('Helvetica', '', 24 * $fScale, '', true);
|
||||
$width = $oPdf->GetStringWidth($this->GetProperty('label'));
|
||||
$oPdf->SetTextColor(0, 0, 0);
|
||||
$oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DisplayableGraph extends SimpleGraph
|
||||
{
|
||||
protected $sDirection;
|
||||
|
||||
public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true)
|
||||
{
|
||||
$oNewGraph = new DisplayableGraph();
|
||||
|
||||
$oNodesIter = new RelationTypeIterator($oGraph, 'Node');
|
||||
foreach($oNodesIter as $oNode)
|
||||
{
|
||||
switch(get_class($oNode))
|
||||
{
|
||||
case 'RelationObjectNode':
|
||||
$oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
|
||||
if ($oNode->GetProperty('source'))
|
||||
{
|
||||
$oNewNode->SetProperty('source', true);
|
||||
}
|
||||
if ($oNode->GetProperty('sink'))
|
||||
{
|
||||
$oNewNode->SetProperty('sink', true);
|
||||
}
|
||||
$oObj = $oNode->GetProperty('object');
|
||||
$oNewNode->SetProperty('class', get_class($oObj));
|
||||
$oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
|
||||
$oNewNode->SetProperty('label', $oObj->GetRawName());
|
||||
$oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
|
||||
$oNewNode->SetProperty('developped', $oNode->GetProperty('developped'));
|
||||
break;
|
||||
|
||||
default:
|
||||
$oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
$oNewNode->SetProperty('label', $oNode->GetProperty('min_up'));
|
||||
$oNewNode->SetProperty('is_reached', true);
|
||||
}
|
||||
}
|
||||
$oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
|
||||
foreach($oEdgesIter as $oEdge)
|
||||
{
|
||||
$oSourceNode = $oNewGraph->GetNode($oEdge->GetSourceNode()->GetId());
|
||||
$oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId());
|
||||
$oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode);
|
||||
}
|
||||
|
||||
// Remove duplicate edges between two nodes
|
||||
$oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
|
||||
$aEdgeKeys = array();
|
||||
foreach($oEdgesIter as $oEdge)
|
||||
{
|
||||
$sSourceId = $oEdge->GetSourceNode()->GetId();
|
||||
$sSinkId = $oEdge->GetSinkNode()->GetId();
|
||||
if ($sSourceId == $sSinkId)
|
||||
{
|
||||
// Remove self referring edges
|
||||
$oNewGraph->_RemoveEdge($oEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = $sSourceId.'//'.$sSinkId;
|
||||
if (array_key_exists($sKey, $aEdgeKeys))
|
||||
{
|
||||
// Remove duplicate edges
|
||||
$oNewGraph->_RemoveEdge($oEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aEdgeKeys[$sKey] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$iNbGrouping = 1;
|
||||
//for($iter=0; $iter<$iNbGrouping; $iter++)
|
||||
{
|
||||
$oNodesIter = new RelationTypeIterator($oNewGraph, 'Node');
|
||||
foreach($oNodesIter as $oNode)
|
||||
{
|
||||
if ($oNode->GetProperty('source'))
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate edges between two nodes
|
||||
$oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
|
||||
$aEdgeKeys = array();
|
||||
foreach($oEdgesIter as $oEdge)
|
||||
{
|
||||
$sSourceId = $oEdge->GetSourceNode()->GetId();
|
||||
$sSinkId = $oEdge->GetSinkNode()->GetId();
|
||||
if ($sSourceId == $sSinkId)
|
||||
{
|
||||
// Remove self referring edges
|
||||
$oNewGraph->_RemoveEdge($oEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = $sSourceId.'//'.$sSinkId;
|
||||
if (array_key_exists($sKey, $aEdgeKeys))
|
||||
{
|
||||
// Remove duplicate edges
|
||||
$oNewGraph->_RemoveEdge($oEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aEdgeKeys[$sKey] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $oNewGraph;
|
||||
}
|
||||
|
||||
public function InitOnGrid()
|
||||
{
|
||||
$iDist = 125;
|
||||
$aAllNodes = $this->_GetNodes();
|
||||
$iSide = ceil(sqrt(count($aAllNodes)));
|
||||
$xPos = 0;
|
||||
$yPos = 0;
|
||||
$idx = 0;
|
||||
foreach($aAllNodes as $oNode)
|
||||
{
|
||||
$xPos += $iDist;
|
||||
if (($idx % $iSide) == 0)
|
||||
{
|
||||
$xPos = 0;
|
||||
$yPos += $iDist;
|
||||
}
|
||||
|
||||
$oNode->x = $xPos;
|
||||
$oNode->y = $yPos;
|
||||
|
||||
$idx++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function InitFromGraphviz()
|
||||
{
|
||||
$sDot = $this->DumpAsXDot();
|
||||
$sDot = preg_replace('/.*label=.*,/', '', $sDot); // Get rid of label lines since they may contain weird characters than can break the split and pattern matching below
|
||||
|
||||
$aChunks = explode(";", $sDot);
|
||||
foreach($aChunks as $sChunk)
|
||||
{
|
||||
//echo "<p>$sChunk</p>";
|
||||
if(preg_match('/"([^"]+)".+pos="([0-9\\.]+),([0-9\\.]+)"/ms', $sChunk, $aMatches))
|
||||
{
|
||||
$sId = $aMatches[1];
|
||||
$xPos = $aMatches[2];
|
||||
$yPos = $aMatches[3];
|
||||
|
||||
$oNode = $this->GetNode($sId);
|
||||
$oNode->x = (float)$xPos;
|
||||
$oNode->y = (float)$yPos;
|
||||
|
||||
//echo "<p>$sId at $xPos,$yPos</p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
//echo "<p>No match</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function BruteForceLayout($iNbTicks, $sDirection = 'horizontal')
|
||||
{
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
$this->sDirection = $sDirection;
|
||||
$this->InitForces();
|
||||
for($i=0; $i<$iNbTicks; $i++)
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
$this->Tick();
|
||||
}
|
||||
}
|
||||
|
||||
protected function InitForces()
|
||||
{
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
$i = 0;
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
$oNode->SetProperty('ax', 0);
|
||||
$oNode->SetProperty('ay', 0);
|
||||
$oNode->SetProperty('vx', 0);
|
||||
$oNode->SetProperty('vy', 0);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
protected function ComputeAcceleration()
|
||||
{
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $idx => $oNode)
|
||||
{
|
||||
$sNodeId = $oNode->GetId();
|
||||
|
||||
$fx = 0;
|
||||
$fy = 0;
|
||||
$K = 0.6;
|
||||
$Q = 0.3;
|
||||
|
||||
if ($oNode->GetProperty('source'))
|
||||
{
|
||||
switch($this->sDirection)
|
||||
{
|
||||
case 'horizontal':
|
||||
$fx -= 30;
|
||||
break;
|
||||
|
||||
case 'vertical':
|
||||
$fy -= 30;
|
||||
break;
|
||||
|
||||
default:
|
||||
// No gravity
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch($this->sDirection)
|
||||
{
|
||||
case 'horizontal':
|
||||
$fx += 30;
|
||||
break;
|
||||
|
||||
case 'vertical':
|
||||
$fy += 30;
|
||||
break;
|
||||
|
||||
default:
|
||||
// No gravity
|
||||
}
|
||||
}
|
||||
|
||||
//echo "<p>ComputeAcceleration - $sNodeId</p>\n";
|
||||
|
||||
$oIter2 = new RelationTypeIterator($this, 'Edge');
|
||||
foreach($oIter2 as $sEdgeId => $oEdge)
|
||||
{
|
||||
$oSource = $oEdge->GetSourceNode();
|
||||
$oSink = $oEdge->GetSinkNode();
|
||||
|
||||
//echo "<p>$sEdgeId ".$oSource->GetId()." -> ".$oSink->GetId()."</p>\n";
|
||||
|
||||
if ($oSource->GetId() === $sNodeId)
|
||||
{
|
||||
$fx += -$K * ($oSource->x - $oSink->x);
|
||||
$fy += -$K * ($oSource->y - $oSink->y);
|
||||
//echo "<p>$sEdgeId Sink - F($fx, $fy)</p>\n";
|
||||
}
|
||||
else if ($oSink->GetId() === $sNodeId)
|
||||
{
|
||||
$fx += -$K * ($oSink->x - $oSource->x);
|
||||
$fy += -$K * ($oSink->y - $oSource->y);
|
||||
//echo "<p>$sEdgeId Source - F($fx, $fy)</p>\n";
|
||||
}
|
||||
// Else do nothing for this node, it's not connected via this edge
|
||||
}
|
||||
$oIter3 = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIter3 as $idx2 => $oOtherNode)
|
||||
{
|
||||
$sOtherId = $oOtherNode->GetId();
|
||||
if ($sOtherId !== $sNodeId)
|
||||
{
|
||||
$d2 = $oOtherNode->Distance2($oNode) / (60*60);
|
||||
if ($d2 < 15)
|
||||
{
|
||||
$dfx = -$Q * ($oOtherNode->x - $oNode->x) / $d2;
|
||||
$dfy = -$Q * ($oOtherNode->y - $oNode->y) / $d2;
|
||||
|
||||
$fx += $dfx;
|
||||
$fy += $dfy;
|
||||
}
|
||||
|
||||
//echo "<p>Electrostatic: $sOtherId d2: $d2 F($dfx, $dfy)</p>\n";
|
||||
|
||||
}
|
||||
}
|
||||
//echo "<p>total forces: $sNodeId d2: $d2 F($fx, $fy)</p>\n";
|
||||
$oNode->SetProperty('ax', $fx);
|
||||
$oNode->SetProperty('ay', $fy);
|
||||
}
|
||||
}
|
||||
|
||||
protected function Tick()
|
||||
{
|
||||
$dt = 0.1;
|
||||
$attenuation = 0.8;
|
||||
$M = 1;
|
||||
|
||||
$this->ComputeAcceleration();
|
||||
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
$vx = $attenuation * $oNode->GetProperty('vx') + $M * $oNode->GetProperty('ax');
|
||||
$vy = $attenuation * $oNode->GetProperty('vy') + $M * $oNode->GetProperty('ay');
|
||||
|
||||
$oNode->x += $dt * $vx;
|
||||
$oNode->y += $dt * $vy;
|
||||
|
||||
$oNode->SetProperty('vx', $vx);
|
||||
$oNode->SetProperty('vy', $vy);
|
||||
//echo "<p>$sId - V($vx, $vy)</p>\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function GetBoundingBox()
|
||||
{
|
||||
$xMin = null;
|
||||
$xMax = null;
|
||||
$yMin = null;
|
||||
$yMax = null;
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
if ($xMin === null) // First element in the loop
|
||||
{
|
||||
$xMin = $oNode->x - $oNode->GetWidth();
|
||||
$xMax = $oNode->x + $oNode->GetWidth();
|
||||
$yMin = $oNode->y - $oNode->GetHeight();
|
||||
$yMax = $oNode->y + $oNode->GetHeight();
|
||||
}
|
||||
else
|
||||
{
|
||||
$xMin = min($xMin, $oNode->x - $oNode->GetWidth() / 2);
|
||||
$xMax = max($xMax, $oNode->x + $oNode->GetWidth() / 2);
|
||||
$yMin = min($yMin, $oNode->y - $oNode->GetHeight() / 2);
|
||||
$yMax = max($yMax, $oNode->y + $oNode->GetHeight() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
return array('xmin' => $xMin, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
|
||||
}
|
||||
|
||||
function Translate($dx, $dy)
|
||||
{
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
$oNode->x += $dx;
|
||||
$oNode->y += $dy;
|
||||
}
|
||||
}
|
||||
|
||||
function RenderAsRaphael(WebPage $oP, $sId = null, $bContinue = false)
|
||||
{
|
||||
if ($sId == null)
|
||||
{
|
||||
$sId = 'graph';
|
||||
}
|
||||
$aBB = $this->GetBoundingBox();
|
||||
$oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
|
||||
$oP->add_ready_script("var oGraph = $('#$sId').simple_graph({xmin: {$aBB['xmin']}, xmax: {$aBB['xmax']}, ymin: {$aBB['ymin']}, ymax: {$aBB['ymax']} });");
|
||||
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
$aNode = $oNode->GetForRaphael();
|
||||
$sJSNode = json_encode($aNode);
|
||||
$oP->add_ready_script("oGraph.simple_graph('add_node', $sJSNode);");
|
||||
}
|
||||
$oIterator = new RelationTypeIterator($this, 'Edge');
|
||||
foreach($oIterator as $sId => $oEdge)
|
||||
{
|
||||
$aEdge = array();
|
||||
$aEdge['id'] = $oEdge->GetId();
|
||||
$aEdge['source_node_id'] = $oEdge->GetSourceNode()->GetId();
|
||||
$aEdge['sink_node_id'] = $oEdge->GetSinkNode()->GetId();
|
||||
$fOpacity = ($oEdge->GetSinkNode()->GetProperty('is_reached') && $oEdge->GetSourceNode()->GetProperty('is_reached') ? 1 : 0.2);
|
||||
$aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000');
|
||||
$sJSEdge = json_encode($aEdge);
|
||||
$oP->add_ready_script("oGraph.simple_graph('add_edge', $sJSEdge);");
|
||||
}
|
||||
|
||||
$oP->add_ready_script("oGraph.simple_graph('draw');");
|
||||
}
|
||||
|
||||
function RenderAsPDF(WebPage $oP, $sTitle = 'Untitled', $sPageFormat = 'A4', $sPageOrientation = 'P')
|
||||
{
|
||||
require_once(APPROOT.'lib/tcpdf/tcpdf.php');
|
||||
$oPdf = new TCPDF($sPageOrientation, 'mm', $sPageFormat, true, 'UTF-8', false);
|
||||
|
||||
// set document information
|
||||
$oPdf->SetCreator(PDF_CREATOR);
|
||||
$oPdf->SetAuthor('iTop');
|
||||
$oPdf->SetTitle($sTitle);
|
||||
|
||||
$oPdf->setFontSubsetting(true);
|
||||
|
||||
// Set font
|
||||
// dejavusans is a UTF-8 Unicode font, if you only need to
|
||||
// print standard ASCII chars, you can use core fonts like
|
||||
// helvetica or times to reduce file size.
|
||||
$oPdf->SetFont('dejavusans', '', 14, '', true);
|
||||
|
||||
// set auto page breaks
|
||||
$oPdf->SetAutoPageBreak(false);
|
||||
|
||||
// Add a page
|
||||
// This method has several options, check the source code documentation for more information.
|
||||
$oPdf->AddPage();
|
||||
|
||||
$aBB = $this->GetBoundingBox();
|
||||
//$this->Translate(-$aBB['xmin'], -$aBB['ymin']);
|
||||
if ($sPageOrientation == 'P')
|
||||
{
|
||||
// Portrait mode
|
||||
$fHMargin = 10; // mm
|
||||
$fVMargin = 15; // mm
|
||||
}
|
||||
else
|
||||
{
|
||||
// Landscape mode
|
||||
$fHMargin = 15; // mm
|
||||
$fVMargin = 10; // mm
|
||||
}
|
||||
|
||||
$fPageW = $oPdf->getPageWidth() - 2 * $fHMargin;
|
||||
$fPageH = $oPdf->getPageHeight() - 2 * $fVMargin;
|
||||
|
||||
$w = $aBB['xmax'] - $aBB['xmin'];
|
||||
$h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons
|
||||
|
||||
$fScale = min($fPageW / $w, $fPageH / $h);
|
||||
$dx = ($fPageW - $fScale * $w) / 2;
|
||||
$dy = ($fPageH - $fScale * $h) / 2;
|
||||
|
||||
$this->Translate(($fHMargin + $dx)/$fScale, ($fVMargin + $dy)/$fScale);
|
||||
|
||||
$oIterator = new RelationTypeIterator($this, 'Edge');
|
||||
foreach($oIterator as $sId => $oEdge)
|
||||
{
|
||||
$oEdge->RenderAsPDF($oPdf, $fScale);
|
||||
}
|
||||
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
{
|
||||
$oNode->RenderAsPDF($oPdf, $fScale);
|
||||
}
|
||||
|
||||
$oP->add($oPdf->Output('iTop.pdf', 'S'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -231,6 +231,7 @@ abstract class MetaModel
|
||||
}
|
||||
|
||||
private static $m_oConfig = null;
|
||||
protected static $m_aModulesParameters = array();
|
||||
|
||||
private static $m_bSkipCheckToWrite = false;
|
||||
private static $m_bSkipCheckExtKeys = false;
|
||||
@@ -1102,6 +1103,11 @@ abstract class MetaModel
|
||||
//
|
||||
private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes)
|
||||
|
||||
/**
|
||||
* TO BE DEPRECATED: use EnumRelationsEx instead
|
||||
* @param string $sClass
|
||||
* @return multitype:string unknown |Ambigous <string, multitype:>
|
||||
*/
|
||||
public static function EnumRelations($sClass = '')
|
||||
{
|
||||
$aResult = array_keys(self::$m_aRelationInfos);
|
||||
@@ -1144,23 +1150,54 @@ abstract class MetaModel
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public static function EnumRelationsEx($sClass)
|
||||
{
|
||||
$aRelationInfo = array_keys(self::$m_aRelationInfos);
|
||||
// Return only the relations that have a meaning (i.e. for which at least one query is defined)
|
||||
// for the specified class
|
||||
$aClassRelations = array();
|
||||
foreach($aRelationInfo as $sRelCode)
|
||||
{
|
||||
$aQueriesDown = self::EnumRelationQueries($sClass, $sRelCode, true /* Down */);
|
||||
if (count($aQueriesDown) > 0)
|
||||
{
|
||||
$aClassRelations[$sRelCode]['down'] = self::GetRelationLabel($sRelCode, true);
|
||||
}
|
||||
|
||||
$aQueriesUp = self::EnumRelationQueries($sClass, $sRelCode, false /* Up */);
|
||||
if (count($aQueriesUp) > 0)
|
||||
{
|
||||
$aClassRelations[$sRelCode]['up'] = self::GetRelationLabel($sRelCode, false);
|
||||
}
|
||||
}
|
||||
|
||||
return $aClassRelations;
|
||||
}
|
||||
|
||||
final static public function GetRelationDescription($sRelCode)
|
||||
{
|
||||
return Dict::S("Relation:$sRelCode/Description");
|
||||
}
|
||||
|
||||
final static public function GetRelationLabel($sRelCode)
|
||||
final static public function GetRelationLabel($sRelCode, $bDown = true)
|
||||
{
|
||||
// The legacy convention is confusing with regard to the way we have conceptualized the relations:
|
||||
// In the former representation, the main stream was named after "up"
|
||||
// Now, the relation from A to B says that something is transmitted from A to B, thus going DOWNstream as described in a petri net.
|
||||
$sKey = "Relation:$sRelCode/DownStream";
|
||||
$sLegacy = Dict::S("Relation:$sRelCode/VerbUp", $sKey);
|
||||
if ($bDown)
|
||||
{
|
||||
// The legacy convention is confusing with regard to the way we have conceptualized the relations:
|
||||
// In the former representation, the main stream was named after "up"
|
||||
// Now, the relation from A to B says that something is transmitted from A to B, thus going DOWNstream as described in a petri net.
|
||||
$sKey = "Relation:$sRelCode/DownStream";
|
||||
$sLegacy = Dict::S("Relation:$sRelCode/VerbUp", $sKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = "Relation:$sRelCode/UpStream";
|
||||
$sLegacy = Dict::S("Relation:$sRelCode/VerbDown", $sKey);
|
||||
}
|
||||
$sRet = Dict::S($sKey, $sLegacy);
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
|
||||
protected static function ComputeRelationQueries($sRelCode)
|
||||
{
|
||||
$bHasLegacy = false;
|
||||
@@ -1339,6 +1376,10 @@ abstract class MetaModel
|
||||
{
|
||||
foreach ($aQueries[$sClass]['up'] as $sNeighbourId => $aNeighbourData)
|
||||
{
|
||||
if (!array_key_exists('_legacy_', $aNeighbourData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!$aNeighbourData['_legacy_']) continue; // Skip modern definitions
|
||||
|
||||
$sLocalClass = $aNeighbourData['sToClass'];
|
||||
@@ -1363,7 +1404,7 @@ abstract class MetaModel
|
||||
$sLocalClass = $aNeighbourData['sFromClass'];
|
||||
foreach (self::EnumChildClasses($aNeighbourData['sToClass'], ENUM_CHILD_CLASSES_ALL) as $sRemoteClass)
|
||||
{
|
||||
$aQueries[$sRemoteClass]['up'][$sLocalClass]['sQueryDown'] = $aNeighbourData['sQueryDown'];
|
||||
//$aQueries[$sRemoteClass]['up'][$sLocalClass]['sQueryDown'] = $aNeighbourData['sQueryDown'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5227,6 +5268,20 @@ abstract class MetaModel
|
||||
return self::$m_oConfig->GetModuleSetting($sModule, $sProperty, $defaultvalue);
|
||||
}
|
||||
|
||||
public static function GetModuleParameter($sModule, $sProperty, $defaultvalue = null)
|
||||
{
|
||||
$value = $defaultvalue;
|
||||
if (!array_key_exists($sModule, self::$m_aModulesParameters))
|
||||
{
|
||||
|
||||
}
|
||||
if (!self::$m_aModulesParameters[$sModule] == null)
|
||||
{
|
||||
$value = self::$m_aModulesParameters[$sModule]->Get($sProperty, $defaultvalue);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function GetConfig()
|
||||
{
|
||||
return self::$m_oConfig;
|
||||
|
||||
@@ -153,6 +153,24 @@ class GraphNode extends GraphElement
|
||||
$this->aOutgoingEdges[$oEdge->GetId()] = $oEdge;
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
* @param GraphEdge $oEdge
|
||||
*/
|
||||
public function _RemoveIncomingEdge(GraphEdge $oEdge)
|
||||
{
|
||||
unset($this->aIncomingEdges[$oEdge->GetId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
* @param GraphEdge $oEdge
|
||||
*/
|
||||
public function _RemoveOutgoingEdge(GraphEdge $oEdge)
|
||||
{
|
||||
unset($this->aOutgoingEdges[$oEdge->GetId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all incoming edges on the current node
|
||||
* @return Ambigous <multitype:, GraphEdge>
|
||||
@@ -171,6 +189,38 @@ class GraphNode extends GraphElement
|
||||
return $this->aOutgoingEdges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flood fill the chart with the given value for the specified property
|
||||
* @param string $sPropName The name of the property to set
|
||||
* @param mixed $value Teh value to set in the property
|
||||
* @param bool $bFloodDown Whether or not to fill in the downstream direction
|
||||
* @param bool $bFloodUp Whether or not to fill in the upstream direction
|
||||
*/
|
||||
public function FloodProperty($sPropName, $value, $bFloodDown, $bFloodUp)
|
||||
{
|
||||
if ($this->GetProperty($sPropName, null) == null)
|
||||
{
|
||||
// Property not already set, let's do it
|
||||
$this->SetProperty($sPropName, $value);
|
||||
if ($bFloodDown)
|
||||
{
|
||||
foreach($this->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$oEdge->SetProperty($sPropName, $value);
|
||||
$oEdge->GetSinkNode()->FloodProperty($sPropName, $value, $bFloodDown, $bFloodUp);
|
||||
}
|
||||
}
|
||||
if ($bFloodUp)
|
||||
{
|
||||
foreach($this->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$oEdge->SetProperty($sPropName, $value);
|
||||
$oEdge->GetSourceNode()->FloodProperty($sPropName, $value, $bFloodDown, $bFloodUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,11 +313,30 @@ class SimpleGraph
|
||||
*/
|
||||
public function _AddNode(GraphNode $oNode)
|
||||
{
|
||||
if (array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot add node (id='.$oNode->GetId().') to the graph. A node with the same id already exists inthe graph.');
|
||||
if (array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot add node (id='.$oNode->GetId().') to the graph. A node with the same id already exists in the graph.');
|
||||
|
||||
$this->aNodes[$oNode->GetId()] = $oNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
* @return Ambigous <multitype:, GraphNode>
|
||||
*/
|
||||
public function _RemoveNode(GraphNode $oNode)
|
||||
{
|
||||
if (!array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot remove the node (id='.$oNode->GetId().') from the graph. The node was not found in the graph.');
|
||||
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
unset($this->aNodes[$oNode->GetId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node identified by $sId or null if not found
|
||||
* @param string $sId
|
||||
@@ -295,13 +364,28 @@ class SimpleGraph
|
||||
*/
|
||||
public function _AddEdge(GraphEdge $oEdge)
|
||||
{
|
||||
if (array_key_exists($oEdge->GetId(), $this->aEdges)) throw new SimpleGraphException('Cannot add edge (id='.$oEdge->GetId().') to the graph. An edge with the same id already exists inthe graph.');
|
||||
if (array_key_exists($oEdge->GetId(), $this->aEdges)) throw new SimpleGraphException('Cannot add edge (id='.$oEdge->GetId().') to the graph. An edge with the same id already exists in the graph.');
|
||||
|
||||
$this->aEdges[$oEdge->GetId()] = $oEdge;
|
||||
$oEdge->GetSourceNode()->_AddOutgoingEdge($oEdge);
|
||||
$oEdge->GetSinkNode()->_AddIncomingEdge($oEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
* @param GraphEdge $oEdge
|
||||
* @throws SimpleGraphException
|
||||
*/
|
||||
public function _RemoveEdge(GraphEdge $oEdge)
|
||||
{
|
||||
if (!array_key_exists($oEdge->GetId(), $this->aEdges)) throw new SimpleGraphException('Cannot remove edge (id='.$oEdge->GetId().') from the graph. The edge was not found.');
|
||||
|
||||
$oEdge->GetSourceNode()->_RemoveOutgoingEdge($oEdge);
|
||||
$oEdge->GetSinkNode()->_RemoveIncomingEdge($oEdge);
|
||||
|
||||
unset($this->aEdges[$oEdge->GetId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edge indentified by $sId or null if not found
|
||||
* @param string $sId
|
||||
@@ -333,8 +417,9 @@ class SimpleGraph
|
||||
digraph finite_state_machine {
|
||||
graph [bgcolor = "transparent"];
|
||||
rankdir=LR;
|
||||
size="30,30"
|
||||
node [ fontname=Verdana style=filled fillcolor="#ffffcc" ];
|
||||
size="30,30";
|
||||
fontsize=8.0;
|
||||
node [ fontname=Verdana style=filled fillcolor="#ffffcc" fontsize=8.0 ];
|
||||
edge [ fontname=Verdana ];
|
||||
|
||||
EOF
|
||||
@@ -413,6 +498,62 @@ EOF
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the graph as an embedded PNG image (using a data: url) as
|
||||
* generated by graphviz (requires graphviz to be installed on the machine and the path to
|
||||
* dot/dot.exe to be configured in the iTop configuration file)
|
||||
* Note: the function creates temporary files in APPROOT/data/tmp
|
||||
* @return string
|
||||
*/
|
||||
public function DumpAsXDot()
|
||||
{
|
||||
$sDotExecutable = MetaModel::GetConfig()->Get('graphviz_path');
|
||||
if (file_exists($sDotExecutable))
|
||||
{
|
||||
// create the file with Graphviz
|
||||
if (!is_dir(APPROOT."data"))
|
||||
{
|
||||
@mkdir(APPROOT."data");
|
||||
}
|
||||
if (!is_dir(APPROOT."data/tmp"))
|
||||
{
|
||||
@mkdir(APPROOT."data/tmp");
|
||||
}
|
||||
$sXdotFilePath = tempnam(APPROOT."data/tmp", 'xdot-');
|
||||
$sDotDescription = $this->GetDotDescription();
|
||||
$sDotFilePath = tempnam(APPROOT."data/tmp", 'dot-');
|
||||
|
||||
$rFile = @fopen($sDotFilePath, "w");
|
||||
@fwrite($rFile, $sDotDescription);
|
||||
@fclose($rFile);
|
||||
$aOutput = array();
|
||||
$CommandLine = "\"$sDotExecutable\" -v -Tdot < $sDotFilePath -o$sXdotFilePath 2>&1";
|
||||
|
||||
exec($CommandLine, $aOutput, $iRetCode);
|
||||
if ($iRetCode != 0)
|
||||
{
|
||||
$sHtml = '';
|
||||
$sHtml .= "<p><b>Error:</b></p>";
|
||||
$sHtml .= "<p>The command: <pre>$CommandLine</pre> returned $iRetCode</p>";
|
||||
$sHtml .= "<p>The output of the command is:<pre>\n".implode("\n", $aOutput)."</pre></p>";
|
||||
$sHtml .= "<hr>";
|
||||
$sHtml .= "<p>Content of the '".basename($sDotFilePath)."' file:<pre>\n$sDotDescription</pre>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml = '<pre>'.file_get_contents($sXdotFilePath).'</pre>';
|
||||
@unlink($sImageFilePath);
|
||||
}
|
||||
@unlink($sXdotFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the description of the graph as some HTML text
|
||||
* @return string
|
||||
@@ -456,6 +597,73 @@ EOF
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the graph in a array of non connected subgraphs
|
||||
* @return multitype:SimpleGraph unknown
|
||||
*/
|
||||
public function GetSubgraphs()
|
||||
{
|
||||
$iNbColors = 0;
|
||||
$aResult = array();
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $oNode)
|
||||
{
|
||||
$iPrevColor = $oNode->GetProperty('color', null);
|
||||
|
||||
if ($iPrevColor == null)
|
||||
{
|
||||
$iNbColors++; // Start a new color
|
||||
$oNode->FloodProperty('color', $iNbColors, true, true);
|
||||
}
|
||||
}
|
||||
if ($iNbColors == 1)
|
||||
{
|
||||
// Everything is connected together, only one subgraph
|
||||
$aResult[] = $this;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's reconstruct each separate graph
|
||||
$sClass = get_class($this);
|
||||
for($i = 1; $i <= $iNbColors; $i++)
|
||||
{
|
||||
$aResult[$i] = new $sClass();
|
||||
}
|
||||
|
||||
foreach($oIterator as $oNode)
|
||||
{
|
||||
$iNodeColor = $oNode->GetProperty('color');
|
||||
$aResult[$iNodeColor]->_AddNode($oNode);
|
||||
}
|
||||
|
||||
$oIter2 = new RelationTypeIterator($this, 'Edge');
|
||||
foreach($oIter2 as $oEdge)
|
||||
{
|
||||
$iEdgeColor = $oEdge->GetProperty('color');
|
||||
$aResult[$iEdgeColor]->_AddEdge($oEdge);
|
||||
}
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge back to subgraphs into one
|
||||
* @param SimpleGraph $oGraph
|
||||
*/
|
||||
public function Merge(SimpleGraph $oGraph)
|
||||
{
|
||||
$oIter1 = new RelationTypeIterator($oGraph, 'Node');
|
||||
foreach($oIter1 as $oNode)
|
||||
{
|
||||
$this->_AddNode($oNode);
|
||||
}
|
||||
$oIter2 = new RelationTypeIterator($oGraph, 'Edge');
|
||||
foreach($oIter2 as $oEdge)
|
||||
{
|
||||
$this->_AddEdge($oEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1419,4 +1419,7 @@ div.ui-dialog-header {
|
||||
.form_field_error {
|
||||
border: 1px solid #933;
|
||||
background: #fcc;
|
||||
}
|
||||
.simple-graph {
|
||||
background: #fff;
|
||||
}
|
||||
574
js/fraphael.js
Normal file
574
js/fraphael.js
Normal file
@@ -0,0 +1,574 @@
|
||||
/**
|
||||
* FRaphael
|
||||
* An extension for Raphael.js to make it easier to work with Filter Effects
|
||||
*
|
||||
* Copyright © 2013 Chris Scott <chris.scott@factmint.com>
|
||||
* Delivered with and licensed under the MIT licence
|
||||
*
|
||||
*/
|
||||
|
||||
// Create the global FRaphael object
|
||||
(function(scope) {
|
||||
var version = "0.0.1",
|
||||
license = "MIT";
|
||||
|
||||
var ns = "http://www.w3.org/2000/svg",
|
||||
idCounter = 0;
|
||||
|
||||
var FR = {
|
||||
// Object prototype for a filter
|
||||
Filter: function(id) {
|
||||
if (id == undefined) {
|
||||
id = "filter-" + idCounter++;
|
||||
while(FR.filters[id] != undefined) {
|
||||
id = "filter-" + idCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (FR.filters[id] != undefined) {
|
||||
throw "A filter with id " + id + " already exists";
|
||||
}
|
||||
|
||||
this.element = document.createElementNS(ns, "filter");
|
||||
this.element.setAttribute("id", id);
|
||||
this.element.setAttribute("x", "-25%");
|
||||
this.element.setAttribute("y", "-25%");
|
||||
this.element.setAttribute("width", "150%");
|
||||
this.element.setAttribute("height", "150%");
|
||||
|
||||
this.lastFEResult = null;
|
||||
|
||||
FR.filters[id] = this;
|
||||
this.id = id;
|
||||
},
|
||||
|
||||
// Object prototype for an effect
|
||||
FilterEffect: function(type, attributes) {
|
||||
this.element = document.createElementNS(ns, type);
|
||||
for (var key in attributes) {
|
||||
this.element.setAttribute(key, attributes[key]);
|
||||
}
|
||||
},
|
||||
|
||||
// Return the filter applied to an element or a new filter if none are currently applied
|
||||
getFilter: function(element) {
|
||||
var filterId = element.data("filterId");
|
||||
var filter = null;
|
||||
|
||||
if (filterId == undefined) {
|
||||
filterId = "element-filter-" + element.id;
|
||||
filter = element.paper.createFilter(filterId);
|
||||
element.filter(filterId);
|
||||
} else {
|
||||
filter = FR.filters[filterId];
|
||||
}
|
||||
|
||||
return filter;
|
||||
},
|
||||
|
||||
// maintain a list of filters by id
|
||||
filters: {}
|
||||
};
|
||||
|
||||
FR.Filter.prototype = {
|
||||
addEffect: function(type, attributes, children) {
|
||||
var effect = new FR.FilterEffect(type, attributes);
|
||||
|
||||
if (children) {
|
||||
if (children instanceof Array) {
|
||||
for (var x in children) {
|
||||
if (!children.hasOwnProperty(x)) continue;
|
||||
|
||||
effect.element.appendChild(children[x].element);
|
||||
}
|
||||
} else {
|
||||
effect.element.appendChild(children.element);
|
||||
}
|
||||
}
|
||||
|
||||
this.element.appendChild(effect.element);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
chainEffect: function(type, attributes, children) {
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
var inId;
|
||||
var outId;
|
||||
if (attributes.in == undefined) {
|
||||
inId = this.getLastResult();
|
||||
} else {
|
||||
inId = attributes.in;
|
||||
}
|
||||
if (attributes.result == undefined) {
|
||||
outId = idCounter++;
|
||||
} else {
|
||||
outId = attributes.result;
|
||||
}
|
||||
|
||||
this.lastFEResult = outId;
|
||||
|
||||
attributes.in = inId;
|
||||
attributes.result = outId;
|
||||
|
||||
this.addEffect(type, attributes, children);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getLastResult: function() {
|
||||
return (this.lastFEResult == undefined) ? "SourceGraphic" : this.lastFEResult;
|
||||
},
|
||||
|
||||
merge: function(in1, in2, attributes) {
|
||||
var mergeNode1 = new FR.FilterEffect("feMergeNode", {
|
||||
in: in1
|
||||
});
|
||||
var mergeNode2 = new FR.FilterEffect("feMergeNode", {
|
||||
in: in2
|
||||
});
|
||||
|
||||
this.chainEffect("feMerge", attributes, [mergeNode1, mergeNode2]);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
compose: function(in1, in2, operator, attributes) {
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
if (operator == undefined) {
|
||||
operator = "over";
|
||||
}
|
||||
|
||||
attributes.in = in1;
|
||||
attributes.in2 = in2;
|
||||
attributes.operator = operator;
|
||||
|
||||
this.chainEffect("feComposite", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
arithmeticCompose: function(in1, in2, k1, k2, k3, k4) {
|
||||
if (k1 == undefined) {
|
||||
k1 = 0;
|
||||
}
|
||||
if (k2 == undefined) {
|
||||
k2 = 0;
|
||||
}
|
||||
if (k3 == undefined) {
|
||||
k3 = 0;
|
||||
}
|
||||
if (k4 == undefined) {
|
||||
k4 = 0;
|
||||
}
|
||||
|
||||
this.compose(in1, in2, "arithmetic", {
|
||||
k1: k1,
|
||||
k2: k2,
|
||||
k3: k3,
|
||||
k4: k4
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addBlur: function(stdDeviation, attributes) {
|
||||
if (!stdDeviation) {
|
||||
throw "Standard deviation is required to perform a blur filter";
|
||||
}
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
attributes.stdDeviation = stdDeviation;
|
||||
|
||||
this.chainEffect("feGaussianBlur", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addOffset: function(dx, dy, attributes) {
|
||||
if (dx == undefined | dy == undefined) {
|
||||
throw "dx and dy values are required to perform an offset FE";
|
||||
}
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
attributes.dx = dx;
|
||||
attributes.dy = dy;
|
||||
|
||||
this.chainEffect("feOffset", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addLighting: function(x, y, z, color, type, attributes) {
|
||||
if (x == undefined | y == undefined | z == undefined) {
|
||||
throw "Three co-ordinates are required to create a light source";
|
||||
}
|
||||
|
||||
var previousResult = this.getLastResult();
|
||||
|
||||
var id = idCounter++;
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
attributes.result = id;
|
||||
if (color != undefined) {
|
||||
attributes["lighting-color"] = color;
|
||||
}
|
||||
|
||||
if (type == undefined || type == "diffuse") {
|
||||
type = "feDiffuseLighting";
|
||||
} else if (type == "specular") {
|
||||
type = "feSpecularLighting";
|
||||
}
|
||||
|
||||
var lightSource = new FR.FilterEffect("fePointLight", {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
});
|
||||
|
||||
this.chainEffect(type, attributes, lightSource).arithmeticCompose(previousResult, id, 3, 0.2, 0, 0);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addShiftToColor: function(color, moveBy, attributes) {
|
||||
if (color == undefined) {
|
||||
throw "A colour string is a required argument to create a colorMatrix";
|
||||
}
|
||||
if (moveBy == undefined) {
|
||||
moveBy = 0.5;
|
||||
}
|
||||
|
||||
var remainingColor = 1 - moveBy, x = remainingColor;
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
var colorObject = Raphael.color(color);
|
||||
var r = colorObject.r * moveBy / 255,
|
||||
g = colorObject.g * moveBy / 255,
|
||||
b = colorObject.b * moveBy / 255;
|
||||
|
||||
/**
|
||||
* r' x 0 0 0 r r
|
||||
* g' 0 x 0 0 g g
|
||||
* b' = 0 0 x 0 b . b
|
||||
* a' 0 0 0 1 0 o
|
||||
* 1 1
|
||||
*/
|
||||
attributes.values = x + " 0 0 0 " + r + " 0 " + x + " 0 0 " + g + " 0 0 " + x + " 0 " + b + " 0 0 0 1 0 ";
|
||||
|
||||
this.chainEffect("feColorMatrix", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addRecolor: function(color, opacity, attributes) {
|
||||
if (color == undefined) {
|
||||
throw "A colour string is a required argument to create a colorMatrix";
|
||||
}
|
||||
if (opacity == undefined) {
|
||||
opacity = 1;
|
||||
}
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
var colorObject = Raphael.color(color);
|
||||
var r = colorObject.r / 255,
|
||||
g = colorObject.g / 255,
|
||||
b = colorObject.b / 255;
|
||||
|
||||
/**
|
||||
* r' 0 0 0 0 r r
|
||||
* g' 0 0 0 0 g g
|
||||
* b' = 0 0 0 0 b . b
|
||||
* a' 0 0 0 a 0 a
|
||||
* 1 1
|
||||
*/
|
||||
attributes.values = "0 0 0 0 " + r + " 0 0 0 0 " + g + " 0 0 0 0 " + b + " 0 0 0 " + opacity + " 0 ";
|
||||
|
||||
this.chainEffect("feColorMatrix", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addDesaturate: function(saturation, attributes) {
|
||||
if (saturation == undefined) {
|
||||
saturnation = 0;
|
||||
}
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
attributes.values = saturation;
|
||||
attributes.type = "saturate";
|
||||
|
||||
this.chainEffect("feColorMatrix", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addConvolveMatrix: function(matrix, attributes) {
|
||||
if (matrix == undefined) {
|
||||
throw "A matrix (usually 9 numbers) must be provided to apply a convolve matrix transform";
|
||||
}
|
||||
|
||||
if (attributes == undefined) {
|
||||
attributes = {};
|
||||
}
|
||||
|
||||
attributes.kernelMatrix = matrix;
|
||||
|
||||
this.chainEffect("feConvolveMatrix", attributes);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
createShadow: function(dx, dy, blur, opacity, color) {
|
||||
if (dx == undefined) {
|
||||
throw "dx is required for the shadow effect";
|
||||
}
|
||||
if (dy == undefined) {
|
||||
throw "dy is required for the shadow effect";
|
||||
}
|
||||
if (blur == undefined) {
|
||||
throw "blur (stdDeviation) is required for the shadow effect";
|
||||
}
|
||||
|
||||
if (opacity == undefined) {
|
||||
opacity = 0.6;
|
||||
}
|
||||
|
||||
var previousResult = this.getLastResult();
|
||||
|
||||
if (color == undefined) {
|
||||
color = "#000000";
|
||||
}
|
||||
|
||||
this.addOffset(dx, dy, {
|
||||
in: "SourceAlpha"
|
||||
});
|
||||
|
||||
this.addRecolor(color, opacity);
|
||||
|
||||
this.addBlur(blur);
|
||||
|
||||
this.merge(this.getLastResult(), previousResult);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
createEmboss: function(height, x, y, z) {
|
||||
if (height == undefined) {
|
||||
height = 2;
|
||||
}
|
||||
if (x == undefined) {
|
||||
x = -1000;
|
||||
}
|
||||
if (y == undefined) {
|
||||
y = -5000;
|
||||
}
|
||||
if (z == undefined) {
|
||||
z = 300;
|
||||
}
|
||||
|
||||
// Create the highlight
|
||||
|
||||
this.addOffset(height * x / (x + y), height * y / (x + y), {
|
||||
in: "SourceAlpha"
|
||||
});
|
||||
|
||||
this.addBlur(height * 0.5);
|
||||
|
||||
var whiteLightSource = new FR.FilterEffect("fePointLight", {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
});
|
||||
|
||||
this.chainEffect("feSpecularLighting", {
|
||||
surfaceScale: height,
|
||||
specularConstant: 0.8,
|
||||
specularExponent: 15
|
||||
}, whiteLightSource);
|
||||
|
||||
this.compose(this.getLastResult(), "SourceAlpha", "in");
|
||||
var whiteLight = this.getLastResult();
|
||||
|
||||
// Create the lowlight
|
||||
|
||||
this.addOffset(height * -1 * x / (x + y), height * -1 * y / (x + y), {
|
||||
in: "SourceAlpha"
|
||||
});
|
||||
|
||||
this.addBlur(height * 0.5);
|
||||
|
||||
var darkLightSource = new FR.FilterEffect("fePointLight", {
|
||||
x: -1 * x,
|
||||
y: -1 * y,
|
||||
z: z
|
||||
});
|
||||
|
||||
this.chainEffect("feSpecularLighting", {
|
||||
surfaceScale: height,
|
||||
specularConstant: 1.8,
|
||||
specularExponent: 6
|
||||
}, darkLightSource);
|
||||
|
||||
this.compose(this.getLastResult(), "SourceAlpha", "in");
|
||||
this.chainEffect("feColorMatrix", {
|
||||
values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
});
|
||||
var darkLight = this.getLastResult();
|
||||
|
||||
this.arithmeticCompose(whiteLight, darkLight, 0, 0.8, 0.5, 0);
|
||||
|
||||
this.merge("SourceGraphic", this.getLastResult());
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
scope.FRaphael = FR;
|
||||
})(this);
|
||||
|
||||
/**
|
||||
* add a filter to the paper by id
|
||||
*/
|
||||
Raphael.fn.createFilter = function(id) {
|
||||
var paper = this;
|
||||
var filter = new FRaphael.Filter(id);
|
||||
paper.defs.appendChild(filter.element);
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply a filter to an element by id
|
||||
*/
|
||||
Raphael.el.filter = function(filter) {
|
||||
var id = (filter instanceof FRaphael.Filter) ? filter.id : filter;
|
||||
|
||||
this.node.setAttribute("filter", "url(#" + id + ")");
|
||||
this.data("filterId", id);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current filter for an element or a new one if not
|
||||
*/
|
||||
Raphael.el.getFilter = function() {
|
||||
return FRaphael.getFilter(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for applying blur
|
||||
*/
|
||||
Raphael.el.blur = function(stdDeviation) {
|
||||
if (stdDeviation == undefined) {
|
||||
stdDeviation = 3;
|
||||
}
|
||||
|
||||
this.getFilter().addBlur(stdDeviation);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for applying a drop shadow
|
||||
*/
|
||||
Raphael.el.shadow = function(dx, dy, blur, opacity, color) {
|
||||
if (dx == undefined) {
|
||||
dx = 3;
|
||||
}
|
||||
if (dy == undefined) {
|
||||
dy = 3;
|
||||
}
|
||||
if (blur == undefined) {
|
||||
blur = 3;
|
||||
}
|
||||
|
||||
this.getFilter().createShadow(dx, dy, blur, opacity, color);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for applying lighting
|
||||
*/
|
||||
Raphael.el.light = function(x, y, z, color, type) {
|
||||
if (x == undefined) {
|
||||
x = this.paper.width;
|
||||
}
|
||||
if (y == undefined) {
|
||||
y = 0;
|
||||
}
|
||||
if (z == undefined) {
|
||||
z = 20;
|
||||
}
|
||||
|
||||
this.getFilter().addLighting(x, y, z, color, type);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for applying a colour shift
|
||||
*/
|
||||
Raphael.el.colorShift = function(color, shift) {
|
||||
if (color == undefined) {
|
||||
color = "black";
|
||||
}
|
||||
if (shift == undefined) {
|
||||
shift = 0.5;
|
||||
}
|
||||
|
||||
this.getFilter().addShiftToColor(color, shift);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for embossing
|
||||
*/
|
||||
Raphael.el.emboss = function(height) {
|
||||
this.getFilter().createEmboss(height);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for desaturating
|
||||
*/
|
||||
Raphael.el.desaturate = function(saturation) {
|
||||
this.getFilter().addDesaturate(saturation);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* A shorthand method for complete desaturation
|
||||
*/
|
||||
Raphael.el.greyScale = function() {
|
||||
this.getFilter().addDesaturate(0);
|
||||
|
||||
return this;
|
||||
};
|
||||
326
js/simple_graph.js
Normal file
326
js/simple_graph.js
Normal file
@@ -0,0 +1,326 @@
|
||||
// jQuery UI style "widget" for displaying a graph
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// graph
|
||||
//
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where "itop" is the namespace,
|
||||
// "dashboard" the widget name
|
||||
$.widget( "itop.simple_graph",
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
xmin: 0,
|
||||
xmax: 0,
|
||||
ymin: 0,
|
||||
ymax: 0,
|
||||
align: 'center',
|
||||
'vertical-align': 'middle'
|
||||
},
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
this.aNodes = [];
|
||||
this.aEdges = [];
|
||||
this.fZoom = 1.0;
|
||||
this.xOffset = 0;
|
||||
this.yOffset = 0;
|
||||
this.iTextHeight = 12;
|
||||
//this.element.height(this.element.parent().height());
|
||||
this.oPaper = Raphael(this.element.get(0), this.element.width(), this.element.height());
|
||||
|
||||
this.auto_scale();
|
||||
|
||||
this.element
|
||||
.addClass('itop-simple-graph');
|
||||
|
||||
this._create_toolkit_menu();
|
||||
},
|
||||
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
this.draw();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
var sId = this.element.attr('id');
|
||||
this.element
|
||||
.removeClass('itop-simple-graph');
|
||||
|
||||
$('#tk_graph'+sId).remove();
|
||||
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
_setOptions: function()
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
// _setOption is called for each individual option that is changing
|
||||
_setOption: function( key, value )
|
||||
{
|
||||
this._superApply(arguments);
|
||||
},
|
||||
draw: function()
|
||||
{
|
||||
this.oPaper.clear();
|
||||
for(var k in this.aNodes)
|
||||
{
|
||||
this._draw_node(this.aNodes[k]);
|
||||
}
|
||||
for(var k in this.aEdges)
|
||||
{
|
||||
this._draw_edge(this.aEdges[k]);
|
||||
}
|
||||
},
|
||||
_draw_node: function(oNode)
|
||||
{
|
||||
var iWidth = oNode.width;
|
||||
var iHeight = 32;
|
||||
var xPos = Math.round(oNode.x * this.fZoom + this.xOffset);
|
||||
var yPos = Math.round(oNode.y * this.fZoom + this.yOffset);
|
||||
oNode.tx = 0;
|
||||
oNode.ty = 0;
|
||||
switch(oNode.shape)
|
||||
{
|
||||
case 'disc':
|
||||
oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
|
||||
var oText = this.oPaper.text(xPos, yPos, oNode.label);
|
||||
oText.attr(oNode.text_attr);
|
||||
oText.transform('s'+this.fZoom);
|
||||
oNode.aElements.push(oText);
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr({fill: '#fff', 'stroke-width':0}));
|
||||
oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
|
||||
var xIcon = xPos - 18 * this.fZoom;
|
||||
var yIcon = yPos - 18 * this.fZoom;
|
||||
oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
|
||||
oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 18*this.fZoom, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
|
||||
oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 9*this.fZoom, yIcon + 18*this.fZoom, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
|
||||
var oText = this.oPaper.text(xPos, yPos +2, oNode.label);
|
||||
oText.attr(oNode.text_attr);
|
||||
oText.transform('s'+this.fZoom);
|
||||
var oBB = oText.getBBox();
|
||||
var dy = iHeight/2*this.fZoom + oBB.height/2;
|
||||
oText.remove();
|
||||
oText = this.oPaper.text(xPos, yPos +dy +2, oNode.label);
|
||||
oText.attr(oNode.text_attr);
|
||||
oText.transform('s'+this.fZoom);
|
||||
oNode.aElements.push(oText);
|
||||
oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}));
|
||||
oText.toFront();
|
||||
break;
|
||||
|
||||
case 'icon':
|
||||
if(Raphael.svg)
|
||||
{
|
||||
// the colorShift plugin works only in SVG
|
||||
oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).colorShift('#fff', 1));
|
||||
}
|
||||
oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).attr(oNode.icon_attr));
|
||||
var oText = this.oPaper.text( xPos, yPos, oNode.label);
|
||||
oText.attr(oNode.text_attr);
|
||||
oText.transform('s'+this.fZoom);
|
||||
var oBB = oText.getBBox();
|
||||
var dy = iHeight/2*this.fZoom + oBB.height/2;
|
||||
oText.remove();
|
||||
oText = this.oPaper.text( xPos, yPos + dy, oNode.label);
|
||||
oText.attr(oNode.text_attr);
|
||||
oText.transform('s'+this.fZoom);
|
||||
oNode.aElements.push(oText);
|
||||
oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}).toBack());
|
||||
break;
|
||||
}
|
||||
if (oNode.source)
|
||||
{
|
||||
oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#c33', 'stroke-width': 3*this.fZoom }).toBack());
|
||||
}
|
||||
if (oNode.sink)
|
||||
{
|
||||
oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#33c', 'stroke-width': 3*this.fZoom }).toBack());
|
||||
}
|
||||
|
||||
var me = this;
|
||||
for(k in oNode.aElements)
|
||||
{
|
||||
var sNodeId = oNode.id;
|
||||
oNode.aElements[k].drag(function(dx, dy, x, y, event) { me._move(sNodeId, dx, dy, x, y, event); }, function(x, y, event) { me._drag_start(sNodeId, x, y, event); }, function (event) { me._drag_end(sNodeId, event); });
|
||||
}
|
||||
},
|
||||
_move: function(sNodeId, dx, dy, x, y, event)
|
||||
{
|
||||
var origDx = dx / this.fZoom;
|
||||
var origDy = dy / this.fZoom;
|
||||
|
||||
var oNode = this._find_node(sNodeId);
|
||||
oNode.x = oNode.xOrig + origDx;
|
||||
oNode.y = oNode.yOrig + origDy;
|
||||
|
||||
for(k in oNode.aElements)
|
||||
{
|
||||
oNode.aElements[k].transform('t'+(oNode.tx + dx)+', '+(oNode.ty + dy));
|
||||
|
||||
for(j in this.aEdges)
|
||||
{
|
||||
var oEdge = this.aEdges[j];
|
||||
if ((oEdge.source_node_id == sNodeId) || (oEdge.sink_node_id == sNodeId))
|
||||
{
|
||||
var sPath = this._get_edge_path(oEdge);
|
||||
oEdge.aElements[0].attr({path: sPath});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_drag_start: function(sNodeId, x, y, event)
|
||||
{
|
||||
var oNode = this._find_node(sNodeId);
|
||||
oNode.xOrig = oNode.x;
|
||||
oNode.yOrig = oNode.y;
|
||||
|
||||
},
|
||||
_drag_end: function(sNodeId, event)
|
||||
{
|
||||
var oNode = this._find_node(sNodeId);
|
||||
oNode.tx += (oNode.x - oNode.xOrig) * this.fZoom;
|
||||
oNode.ty += (oNode.y - oNode.yOrig) * this.fZoom;
|
||||
oNode.xOrig = oNode.x;
|
||||
oNode.yOrig = oNode.y;
|
||||
},
|
||||
_get_edge_path: function(oEdge)
|
||||
{
|
||||
var oStart = this._find_node(oEdge.source_node_id);
|
||||
var oEnd = this._find_node(oEdge.sink_node_id);
|
||||
var iArrowSize = 5;
|
||||
|
||||
if ((oStart == null) || (oEnd == null)) return '';
|
||||
|
||||
var xStart = Math.round(oStart.x * this.fZoom + this.xOffset);
|
||||
var yStart = Math.round(oStart.y * this.fZoom + this.yOffset);
|
||||
var xEnd = Math.round(oEnd.x * this.fZoom + this.xOffset);
|
||||
var yEnd = Math.round(oEnd.y * this.fZoom + this.yOffset);
|
||||
|
||||
var sPath = Raphael.format('M{0},{1}L{2},{3}', xStart, yStart, xEnd, yEnd);
|
||||
var vx = (xEnd - xStart);
|
||||
var vy = (yEnd - yStart);
|
||||
var l = Math.sqrt(vx*vx+vy*vy);
|
||||
vx = vx / l;
|
||||
vy = vy / l;
|
||||
var ux = -vy;
|
||||
var uy = vx;
|
||||
var lPos = Math.max(l/2, l - 40*this.fZoom);
|
||||
var xArrow = xStart + vx * lPos;
|
||||
var yArrow = yStart + vy * lPos;
|
||||
sPath += Raphael.format('M{0},{1}l{2},{3}M{4},{5}l{6},{7}', xArrow, yArrow, this.fZoom * iArrowSize *(-vx + ux), this.fZoom * iArrowSize *(-vy + uy), xArrow, yArrow, this.fZoom * iArrowSize *(-vx - ux), this.fZoom * iArrowSize *(-vy - uy));
|
||||
return sPath;
|
||||
},
|
||||
_draw_edge: function(oEdge)
|
||||
{
|
||||
var fStrokeSize = Math.max(1, 2 * this.fZoom);
|
||||
var sPath = this._get_edge_path(oEdge);
|
||||
var oAttr = $.extend(oEdge.attr);
|
||||
oAttr['stroke-linecap'] = 'round';
|
||||
oAttr['stroke-width'] = fStrokeSize;
|
||||
oEdge.aElements.push(this.oPaper.path(sPath).attr(oAttr).toBack());
|
||||
},
|
||||
_find_node: function(sId)
|
||||
{
|
||||
for(var k in this.aNodes)
|
||||
{
|
||||
if (this.aNodes[k].id == sId) return this.aNodes[k];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
auto_scale: function()
|
||||
{
|
||||
var fMaxZoom = 1.5;
|
||||
iMargin = 10;
|
||||
xmin = this.options.xmin - iMargin;
|
||||
xmax = this.options.xmax + iMargin;
|
||||
ymin = this.options.ymin - iMargin;
|
||||
ymax = this.options.ymax + iMargin;
|
||||
var xScale = this.element.width() / (xmax - xmin);
|
||||
var yScale = this.element.height() / (ymax - ymin + this.iTextHeight);
|
||||
|
||||
this.fZoom = Math.min(xScale, yScale, fMaxZoom);
|
||||
switch(this.options.align)
|
||||
{
|
||||
case 'left':
|
||||
this.xOffset = -xmin * this.fZoom;
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom);
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom) / 2;
|
||||
break;
|
||||
}
|
||||
switch(this.options['vertical-align'])
|
||||
{
|
||||
case 'top':
|
||||
this.yOffset = -ymin * this.fZoom;
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
this.yOffset = this.element.height() - (ymax + this.iTextHeight) * this.fZoom;
|
||||
break;
|
||||
|
||||
case 'middle':
|
||||
this.yOffset = (this.element.height() - (ymax - ymin + this.iTextHeight) * this.fZoom) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
add_node: function(oNode)
|
||||
{
|
||||
oNode.aElements = [];
|
||||
this.aNodes.push(oNode);
|
||||
},
|
||||
add_edge: function(oEdge)
|
||||
{
|
||||
oEdge.aElements = [];
|
||||
this.aEdges.push(oEdge);
|
||||
},
|
||||
_create_toolkit_menu: function()
|
||||
{
|
||||
var sPopupMenuId = 'tk_graph'+this.element.attr('id');
|
||||
var sHtml = '<div class="itop_popup toolkit_menu" style="font-size: 12px;" id="'+sPopupMenuId+'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
|
||||
sHtml += '<li><a href="#" id="'+sPopupMenuId+'_pdf">Export as PDF</a></li>';
|
||||
sHtml += '<li><a href="#" id="'+sPopupMenuId+'_document">Export as document...</a></li>';
|
||||
sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
|
||||
sHtml += '</ul></li></ul></div>';
|
||||
|
||||
this.element.before(sHtml);
|
||||
$('#'+sPopupMenuId).popupmenu();
|
||||
|
||||
var me = this;
|
||||
$('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); });
|
||||
$('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); });
|
||||
$('#'+sPopupMenuId+'_reload').click(function() { me.reload(); });
|
||||
|
||||
},
|
||||
export_as_pdf: function()
|
||||
{
|
||||
alert('Export as PDF: not yet implemented');
|
||||
},
|
||||
export_as_document: function()
|
||||
{
|
||||
alert('Export as document: not yet implemented');
|
||||
},
|
||||
reload: function()
|
||||
{
|
||||
alert('Reload: not yet implemented');
|
||||
}
|
||||
});
|
||||
});
|
||||
90
pages/UI.php
90
pages/UI.php
@@ -239,7 +239,7 @@ function DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj)
|
||||
$oP->add("</div>");
|
||||
}
|
||||
|
||||
function DisplayNavigatorGraphicsTab($oP, $aResults, $sClass, $id, $sRelation, $oAppContext)
|
||||
function DisplayNavigatorGraphicsTab($oP, $aResults, $oRelGraph, $sClass, $id, $sRelation, $oAppContext, $bDirectionDown)
|
||||
{
|
||||
$oP->SetCurrentTab(Dict::S('UI:RelationshipGraph'));
|
||||
|
||||
@@ -273,42 +273,23 @@ EOF
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("<div class=\"HRDrawer\"></div>\n");
|
||||
$oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">".Dict::S('UI:ElementsDisplayed')."</div>\n");
|
||||
|
||||
$sDirection = utils::ReadParam('d', 'horizontal');
|
||||
$iGroupingThreshold = utils::ReadParam('g', 5);
|
||||
|
||||
$width = 1000;
|
||||
$height = 700;
|
||||
$sDrillUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&'.$oAppContext->GetForLink();
|
||||
$sParams = "pWidth=$width&pHeight=$height&drillUrl=".urlencode($sDrillUrl)."&displayController=false&xmlUrl=".urlencode("./xml.navigator.php")."&obj_class=$sClass&obj_id=$id&relation=$sRelation";
|
||||
|
||||
$oP->add("<div style=\"z-index:1;background:white;width:100%;height:{$height}px\"><object style=\"z-index:2\" classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0\" width=\"100%\" height=\"$height\" id=\"navigator\" align=\"middle\">
|
||||
<param name=\"allowScriptAccess\" value=\"always\" />
|
||||
<param name=\"allowFullScreen\" value=\"false\" />
|
||||
<param name=\"FlashVars\" value=\"$sParams\" />
|
||||
<param name=\"wmode\" value=\"transparent\">
|
||||
<param name=\"movie\" value=\"../navigator/navigator.swf\" /><param name=\"quality\" value=\"high\" /><param name=\"bgcolor\" value=\"#ffffff\" />
|
||||
<embed src=\"../navigator/navigator.swf\" wmode=\"transparent\" flashVars=\"$sParams\" quality=\"high\" bgcolor=\"#ffffff\" width=\"100%\" height=\"$height\" name=\"navigator\" align=\"middle\" swliveconnect=\"true\" allowScriptAccess=\"always\" allowFullScreen=\"false\" type=\"application/x-shockwave-flash\" pluginspage=\"http://www.adobe.com/go/getflashplayer\" />
|
||||
</object></div>\n");
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
|
||||
$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, $bDirectionDown);
|
||||
$oGraph->InitFromGraphviz();
|
||||
$oGraph->RenderAsRaphael($oP);
|
||||
$oP->p('<a target="_blank" href="'.utils::GetAbsoluteUrlAppRoot().'/pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($bDirectionDown ? 'down' : 'up').'&class='.$sClass.'&id='.$id.'">'.Dict::S('UI:GraphAsPDF').'</a>');
|
||||
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
function getFlashMovieObject(movieName)
|
||||
{
|
||||
if (window.document[movieName])
|
||||
{
|
||||
return window.document[movieName];
|
||||
}
|
||||
if (navigator.appName.indexOf("Microsoft Internet")==-1)
|
||||
{
|
||||
if (document.embeds && document.embeds[movieName])
|
||||
return document.embeds[movieName];
|
||||
}
|
||||
else // if (navigator.appName.indexOf("Microsoft Internet")!=-1)
|
||||
{
|
||||
return document.getElementById(movieName);
|
||||
}
|
||||
}
|
||||
|
||||
function DoReload()
|
||||
{
|
||||
$('#ReloadMovieBtn').button('disable');
|
||||
var oMovie = getFlashMovieObject('navigator');
|
||||
try
|
||||
{
|
||||
var aExcluded = [];
|
||||
@@ -318,8 +299,7 @@ function getFlashMovieObject(movieName)
|
||||
aExcluded.push($(this).val());
|
||||
}
|
||||
} );
|
||||
oMovie.Filter(aExcluded.join(','));
|
||||
//oMovie.SetVariable("/:message", "foo");
|
||||
alert('Not yet implemented');
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
@@ -352,8 +332,7 @@ EOF
|
||||
ajax_request = $.get(GetAbsoluteUrlAppRoot()+'pages/xml.navigator.php', { 'class': sClass, id: iId, relation: sRelation, format: 'html' },
|
||||
function(data)
|
||||
{
|
||||
$('#impacted_objects').empty();
|
||||
$('#impacted_objects').append(data);
|
||||
alert('Not yet implemented');
|
||||
$('#impacted_objects').unblock();
|
||||
}
|
||||
);
|
||||
@@ -1530,14 +1509,47 @@ EOF
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
case 'swf_navigator': // Graphical display of the relations "impact" / "depends on"
|
||||
require_once(APPROOT.'core/simplegraph.class.inc.php');
|
||||
require_once(APPROOT.'core/relationgraph.class.inc.php');
|
||||
require_once(APPROOT.'core/displayablegraph.class.inc.php');
|
||||
$sClass = utils::ReadParam('class', '', false, 'class');
|
||||
$id = utils::ReadParam('id', 0);
|
||||
$sRelation = utils::ReadParam('relation', 'impact');
|
||||
$sDirection = utils::ReadParam('direction', 'down');
|
||||
|
||||
$aResults = array();
|
||||
$oObj = MetaModel::GetObject($sClass, $id);
|
||||
$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
|
||||
$oObj->GetRelatedObjects($sRelation, $iMaxRecursionDepth /* iMaxDepth */, $aResults);
|
||||
$aSourceObjects = array($oObj);
|
||||
if ($sRelation == 'depends on')
|
||||
{
|
||||
$sRelation = 'impacts';
|
||||
$sDirection = 'up';
|
||||
}
|
||||
if ($sDirection == 'up')
|
||||
{
|
||||
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
|
||||
}
|
||||
|
||||
|
||||
$aResults = array();
|
||||
$oIterator = new RelationTypeIterator($oRelGraph, 'Node');
|
||||
foreach($oIterator as $oNode)
|
||||
{
|
||||
$oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes) do not contain an object
|
||||
if ($oObj)
|
||||
{
|
||||
$sObjClass = get_class($oObj);
|
||||
if (!array_key_exists($sClass, $aResults))
|
||||
{
|
||||
$aResults[$sObjClass] = array();
|
||||
}
|
||||
$aResults[$sObjClass][] = $oObj;
|
||||
}
|
||||
}
|
||||
|
||||
$oP->AddTabContainer('Navigator');
|
||||
$oP->SetCurrentTabContainer('Navigator');
|
||||
@@ -1546,11 +1558,11 @@ EOF
|
||||
if ($sFirstTab == 'list')
|
||||
{
|
||||
DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj);
|
||||
DisplayNavigatorGraphicsTab($oP, $aResults, $sClass, $id, $sRelation, $oAppContext);
|
||||
DisplayNavigatorGraphicsTab($oP, $aResults, $oRelGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down'));
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayNavigatorGraphicsTab($oP, $aResults, $sClass, $id, $sRelation, $oAppContext);
|
||||
DisplayNavigatorGraphicsTab($oP, $aResults, $oRelGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down'));
|
||||
DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -1724,7 +1724,42 @@ EOF
|
||||
// Stop & cleanup an export...
|
||||
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||
ExcelExporter::CleanupFromToken($sToken);
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'relation_pdf':
|
||||
require_once(APPROOT.'core/simplegraph.class.inc.php');
|
||||
require_once(APPROOT.'core/relationgraph.class.inc.php');
|
||||
require_once(APPROOT.'core/displayablegraph.class.inc.php');
|
||||
$sClass = utils::ReadParam('class', '', false, 'class');
|
||||
$id = utils::ReadParam('id', 0);
|
||||
$sRelation = utils::ReadParam('relation', 'impact');
|
||||
$sDirection = utils::ReadParam('direction', 'down');
|
||||
|
||||
$iGroupingThreshold = utils::ReadParam('g', 5);
|
||||
$sPageFormat = utils::ReadParam('p', 'A4');
|
||||
$sPageOrientation = utils::ReadParam('o', 'L');
|
||||
$sTitle = utils::ReadParam('title', '', false, 'raw_data');
|
||||
|
||||
$oObj = MetaModel::GetObject($sClass, $id);
|
||||
$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
|
||||
$aSourceObjects = array($oObj);
|
||||
if ($sDirection == 'up')
|
||||
{
|
||||
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
|
||||
}
|
||||
|
||||
|
||||
$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
|
||||
$oGraph->InitFromGraphviz();
|
||||
$oGraph->RenderAsPDF($oPage, $sTitle, $sPageFormat, $sPageOrientation);
|
||||
|
||||
$oPage->SetContentType('application/pdf');
|
||||
$oPage->SetContentDisposition('inline', 'iTop.pdf');
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user