displayablegraph.class.inc.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. <?php
  2. // Copyright (C) 2015 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Special kind of Graph for producing some nice output
  20. *
  21. * @copyright Copyright (C) 2015 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. class DisplayableNode extends GraphNode
  25. {
  26. public $x;
  27. public $y;
  28. /**
  29. * Create a new node inside a graph
  30. * @param SimpleGraph $oGraph
  31. * @param string $sId The unique identifier of this node inside the graph
  32. * @param number $x Horizontal position
  33. * @param number $y Vertical position
  34. */
  35. public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
  36. {
  37. parent::__construct($oGraph, $sId);
  38. $this->x = $x;
  39. $this->y = $y;
  40. $this->bFiltered = false;
  41. }
  42. public function GetIconURL()
  43. {
  44. return $this->GetProperty('icon_url', '');
  45. }
  46. public function GetLabel()
  47. {
  48. return $this->GetProperty('label', $this->sId);
  49. }
  50. public function GetWidth()
  51. {
  52. return max(32, 5*strlen($this->GetProperty('label'))); // approximation of the text's bounding box
  53. }
  54. public function GetHeight()
  55. {
  56. return 32;
  57. }
  58. public function Distance2(DisplayableNode $oNode)
  59. {
  60. $dx = $this->x - $oNode->x;
  61. $dy = $this->y - $oNode->y;
  62. $d2 = $dx*$dx + $dy*$dy - $this->GetHeight()*$this->GetHeight();
  63. if ($d2 < 40)
  64. {
  65. $d2 = 40;
  66. }
  67. return $d2;
  68. }
  69. public function Distance(DisplayableNode $oNode)
  70. {
  71. return sqrt($this->Distance2($oNode));
  72. }
  73. public function GetForRaphael()
  74. {
  75. $aNode = array();
  76. $aNode['shape'] = 'icon';
  77. $aNode['icon_url'] = $this->GetIconURL();
  78. $aNode['width'] = 32;
  79. $aNode['source'] = ($this->GetProperty('source') == true);
  80. $aNode['sink'] = ($this->GetProperty('sink') == true);
  81. $aNode['x'] = $this->x;
  82. $aNode['y']= $this->y;
  83. $aNode['label'] = $this->GetLabel();
  84. $aNode['id'] = $this->GetId();
  85. $fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  86. $aNode['icon_attr'] = array('opacity' => $fOpacity);
  87. $aNode['text_attr'] = array('opacity' => $fOpacity);
  88. return $aNode;
  89. }
  90. public function RenderAsPDF(TCPDF $oPdf, $fScale)
  91. {
  92. $Alpha = 1.0;
  93. $oPdf->SetFillColor(200, 200, 200);
  94. $oPdf->setAlpha(1);
  95. $sIconUrl = $this->GetProperty('icon_url');
  96. $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
  97. if ($this->GetProperty('source'))
  98. {
  99. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
  100. $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
  101. }
  102. else if ($this->GetProperty('sink'))
  103. {
  104. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
  105. $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
  106. }
  107. if (!$this->GetProperty('is_reached'))
  108. {
  109. if (function_exists('imagecreatefrompng'))
  110. {
  111. $im = imagecreatefrompng($sIconPath);
  112. if($im && imagefilter($im, IMG_FILTER_COLORIZE, 255, 255, 255))
  113. {
  114. $sTempImageName = APPROOT.'data/tmp-'.basename($sIconPath);
  115. imagesavealpha($im, true);
  116. imagepng($im, $sTempImageName);
  117. imagedestroy($im);
  118. $oPdf->Image($sTempImageName, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
  119. }
  120. }
  121. $Alpha = 0.4;
  122. $oPdf->setAlpha($Alpha);
  123. }
  124. $oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
  125. //$oPdf->Image(APPROOT.'images/blank-100x100.png', ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale, '', '', '', false, 300, '', false, $mask);
  126. //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())
  127. $oPdf->SetFont('Helvetica', '', 24 * $fScale, '', true);
  128. $width = $oPdf->GetStringWidth($this->GetProperty('label'));
  129. $height = $oPdf->GetStringHeight(1000, $this->GetProperty('label'));
  130. $oPdf->setAlpha(0.6 * $Alpha);
  131. $oPdf->SetFillColor(255, 255, 255);
  132. $oPdf->SetDrawColor(255, 255, 255);
  133. $oPdf->Rect($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $width, $height, 'DF');
  134. $oPdf->setAlpha($Alpha);
  135. $oPdf->SetTextColor(0, 0, 0);
  136. $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $this->GetProperty('label'));
  137. }
  138. public function GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
  139. {
  140. //echo "<p>".$this->GetProperty('label').":</p>";
  141. if ($this->GetProperty('grouped') === true) return;
  142. $this->SetProperty('grouped', true);
  143. if ($bDirectionDown)
  144. {
  145. $aNodesPerClass = array();
  146. foreach($this->GetOutgoingEdges() as $oEdge)
  147. {
  148. $oNode = $oEdge->GetSinkNode();
  149. if ($oNode->GetProperty('class') !== null)
  150. {
  151. $sClass = $oNode->GetProperty('class');
  152. if (($sClass!== null) && (!array_key_exists($sClass, $aNodesPerClass)))
  153. {
  154. $aNodesPerClass[$sClass] = array(
  155. 'reached' => array(
  156. 'count' => 0,
  157. 'nodes' => array(),
  158. 'icon_url' => $oNode->GetProperty('icon_url'),
  159. ),
  160. 'not_reached' => array(
  161. 'count' => 0,
  162. 'nodes' => array(),
  163. 'icon_url' => $oNode->GetProperty('icon_url'),
  164. )
  165. );
  166. }
  167. $sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
  168. if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
  169. {
  170. $aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
  171. $aNodesPerClass[$sClass][$sKey]['count'] += (int)$oNode->GetProperty('count', 1);
  172. //echo "<p>New count: ".$aNodesPerClass[$sClass][$sKey]['count']."</p>";
  173. }
  174. }
  175. else
  176. {
  177. $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  178. }
  179. }
  180. foreach($aNodesPerClass as $sClass => $aDefs)
  181. {
  182. foreach($aDefs as $sStatus => $aGroupProps)
  183. {
  184. //echo "<p>$sClass/$sStatus: {$aGroupProps['count']} object(s), actually: ".count($aGroupProps['nodes'])."</p>";
  185. if (count($aGroupProps['nodes']) >= $iThresholdCount)
  186. {
  187. $oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.$sClass);
  188. $oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
  189. $oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
  190. $oNewNode->SetProperty('class', $sClass);
  191. $oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
  192. $oNewNode->SetProperty('count', $aGroupProps['count']);
  193. //$oNewNode->SetProperty('grouped', true);
  194. $oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
  195. foreach($aGroupProps['nodes'] as $oNode)
  196. {
  197. foreach($oNode->GetIncomingEdges() as $oEdge)
  198. {
  199. if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
  200. {
  201. $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
  202. }
  203. }
  204. foreach($oNode->GetOutgoingEdges() as $oEdge)
  205. {
  206. $aOutgoing[] = $oEdge->GetSinkNode();
  207. try
  208. {
  209. $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
  210. }
  211. catch(Exception $e)
  212. {
  213. // ignore this edge
  214. }
  215. }
  216. if ($oGraph->GetNode($oNode->GetId()))
  217. {
  218. $oGraph->_RemoveNode($oNode);
  219. }
  220. }
  221. $oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  222. }
  223. else
  224. {
  225. foreach($aGroupProps['nodes'] as $oNode)
  226. {
  227. $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  228. }
  229. }
  230. }
  231. }
  232. }
  233. }
  234. }
  235. class DisplayableRedundancyNode extends DisplayableNode
  236. {
  237. public function GetWidth()
  238. {
  239. return 24;
  240. }
  241. public function GetForRaphael()
  242. {
  243. $aNode = array();
  244. $aNode['shape'] = 'disc';
  245. $aNode['icon_url'] = $this->GetIconURL();
  246. $aNode['source'] = ($this->GetProperty('source') == true);
  247. $aNode['width'] = $this->GetWidth();
  248. $aNode['x'] = $this->x;
  249. $aNode['y']= $this->y;
  250. $aNode['label'] = $this->GetLabel();
  251. $aNode['id'] = $this->GetId();
  252. $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
  253. $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#c33', 'opacity' => $fDiscOpacity);
  254. $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  255. $aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity);
  256. return $aNode;
  257. }
  258. public function RenderAsPDF(TCPDF $oPdf, $fScale)
  259. {
  260. $oPdf->SetAlpha(1);
  261. $oPdf->SetFillColor(200, 0, 0);
  262. $oPdf->SetDrawColor(0, 0, 0);
  263. $oPdf->Circle($this->x*$fScale, $this->y*$fScale, 16*$fScale, 0, 360, 'DF');
  264. $oPdf->SetTextColor(255, 255, 255);
  265. $oPdf->SetFont('Helvetica', '', 28 * $fScale, '', true);
  266. $sLabel = (string)$this->GetProperty('label');
  267. $width = $oPdf->GetStringWidth($sLabel, 'Helvetica', 'B', 24*$fScale);
  268. $height = $oPdf->GetStringHeight(1000, $sLabel);
  269. $xPos = (float)$this->x*$fScale - $width/2;
  270. $yPos = (float)$this->y*$fScale - $height/2;
  271. // $oPdf->Rect($xPos, $yPos, $width, $height, 'D');
  272. // $oPdf->Text($xPos, $yPos, $sLabel);
  273. $oPdf->SetXY(($this->x - 16)*$fScale, ($this->y - 16)*$fScale);
  274. // text on center
  275. $oPdf->Cell(32*$fScale, 32*$fScale, $sLabel, 0, 0, 'C', 0, '', 0, false, 'T', 'C');
  276. }
  277. public function GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
  278. {
  279. parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  280. if ($bDirectionUp)
  281. {
  282. $aNodesPerClass = array();
  283. foreach($this->GetIncomingEdges() as $oEdge)
  284. {
  285. $oNode = $oEdge->GetSourceNode();
  286. if (($oNode->GetProperty('class') !== null) && (!$oNode->GetProperty('is_reached')))
  287. {
  288. $sClass = $oNode->GetProperty('class');
  289. if (!array_key_exists($sClass, $aNodesPerClass))
  290. {
  291. $aNodesPerClass[$sClass] = array('reached' => array(), 'not_reached' => array());
  292. }
  293. $aNodesPerClass[$sClass][$oNode->GetProperty('is_reached') ? 'reached' : 'not_reached'][] = $oNode;
  294. }
  295. else
  296. {
  297. //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  298. }
  299. }
  300. foreach($aNodesPerClass as $sClass => $aDefs)
  301. {
  302. foreach($aDefs as $sStatus => $aNodes)
  303. {
  304. //echo "<p>".$this->GetId().' has '.count($aNodes)." neighbours of class $sClass in status $sStatus\n";
  305. if (count($aNodes) >= $iThresholdCount)
  306. {
  307. $oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
  308. $oNewNode->SetProperty('label', 'x'.count($aNodes));
  309. $oNewNode->SetProperty('icon_url', $aNodes[0]->GetProperty('icon_url'));
  310. $oNewNode->SetProperty('is_reached', $aNodes[0]->GetProperty('is_reached'));
  311. $oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
  312. foreach($aNodes as $oNode)
  313. {
  314. foreach($oNode->GetIncomingEdges() as $oEdge)
  315. {
  316. $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
  317. }
  318. foreach($oNode->GetOutgoingEdges() as $oEdge)
  319. {
  320. if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
  321. {
  322. $aOutgoing[] = $oEdge->GetSinkNode();
  323. $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
  324. }
  325. }
  326. //echo "<p>Replacing ".$oNode->GetId().' by '.$oNewNode->GetId()."\n";
  327. $oGraph->_RemoveNode($oNode);
  328. }
  329. //$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  330. }
  331. else
  332. {
  333. foreach($aNodes as $oNode)
  334. {
  335. //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  336. }
  337. }
  338. }
  339. }
  340. }
  341. }
  342. }
  343. class DisplayableEdge extends GraphEdge
  344. {
  345. public function RenderAsPDF(TCPDF $oPdf, $fScale)
  346. {
  347. $xStart = $this->GetSourceNode()->x * $fScale;
  348. $yStart = $this->GetSourceNode()->y * $fScale;
  349. $xEnd = $this->GetSinkNode()->x * $fScale;
  350. $yEnd = $this->GetSinkNode()->y * $fScale;
  351. $bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached'));
  352. $oPdf->setAlpha(1);
  353. if ($bReached)
  354. {
  355. $aColor = array(100, 100, 100);
  356. }
  357. else
  358. {
  359. $aColor = array(200, 200, 200);
  360. }
  361. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aColor));
  362. $oPdf->Line($xStart, $yStart, $xEnd, $yEnd);
  363. $vx = $xEnd - $xStart;
  364. $vy = $yEnd - $yStart;
  365. $l = sqrt($vx*$vx + $vy*$vy);
  366. $vx = $vx / $l;
  367. $vy = $vy / $l;
  368. $ux = -$vy;
  369. $uy = $vx;
  370. $lPos = max($l/2, $l - 40*$fScale);
  371. $iArrowSize = 5*$fScale;
  372. $x = $xStart + $lPos * $vx;
  373. $y = $yStart + $lPos * $vy;
  374. $oPdf->Line($x, $y, $x + $iArrowSize * ($ux-$vx), $y + $iArrowSize * ($uy-$vy));
  375. $oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy));
  376. }
  377. }
  378. class DisplayableGroupNode extends DisplayableNode
  379. {
  380. public function GetWidth()
  381. {
  382. return 50;
  383. }
  384. public function GetForRaphael()
  385. {
  386. $aNode = array();
  387. $aNode['shape'] = 'group';
  388. $aNode['icon_url'] = $this->GetIconURL();
  389. $aNode['source'] = ($this->GetProperty('source') == true);
  390. $aNode['width'] = $this->GetWidth();
  391. $aNode['x'] = $this->x;
  392. $aNode['y']= $this->y;
  393. $aNode['label'] = $this->GetLabel();
  394. $aNode['id'] = $this->GetId();
  395. $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
  396. $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  397. $aNode['icon_attr'] = array('opacity' => $fTextOpacity);
  398. $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#fff', 'opacity' => $fDiscOpacity);
  399. $aNode['text_attr'] = array('fill' => '#000', 'opacity' => $fTextOpacity);
  400. return $aNode;
  401. }
  402. public function RenderAsPDF(TCPDF $oPdf, $fScale)
  403. {
  404. $bReached = $this->GetProperty('is_reached');
  405. $oPdf->SetFillColor(255, 255, 255);
  406. if ($bReached)
  407. {
  408. $aBorderColor = array(100, 100, 100);
  409. }
  410. else
  411. {
  412. $aBorderColor = array(200, 200, 200);
  413. }
  414. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
  415. $sIconUrl = $this->GetProperty('icon_url');
  416. $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
  417. $oPdf->SetAlpha(1);
  418. $oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
  419. if ($bReached)
  420. {
  421. $oPdf->SetAlpha(1);
  422. }
  423. else
  424. {
  425. $oPdf->SetAlpha(0.4);
  426. }
  427. $oPdf->Image($sIconPath, ($this->x - 17)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
  428. $oPdf->Image($sIconPath, ($this->x + 1)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
  429. $oPdf->Image($sIconPath, ($this->x -8)*$fScale, ($this->y +1)*$fScale, 16*$fScale, 16*$fScale);
  430. $oPdf->SetFont('Helvetica', '', 24 * $fScale, '', true);
  431. $width = $oPdf->GetStringWidth($this->GetProperty('label'));
  432. $oPdf->SetTextColor(0, 0, 0);
  433. $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label'));
  434. }
  435. }
  436. class DisplayableGraph extends SimpleGraph
  437. {
  438. protected $sDirection;
  439. public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true)
  440. {
  441. $oNewGraph = new DisplayableGraph();
  442. $oNodesIter = new RelationTypeIterator($oGraph, 'Node');
  443. foreach($oNodesIter as $oNode)
  444. {
  445. switch(get_class($oNode))
  446. {
  447. case 'RelationObjectNode':
  448. $oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
  449. if ($oNode->GetProperty('source'))
  450. {
  451. $oNewNode->SetProperty('source', true);
  452. }
  453. if ($oNode->GetProperty('sink'))
  454. {
  455. $oNewNode->SetProperty('sink', true);
  456. }
  457. $oObj = $oNode->GetProperty('object');
  458. $oNewNode->SetProperty('class', get_class($oObj));
  459. $oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
  460. $oNewNode->SetProperty('label', $oObj->GetRawName());
  461. $oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
  462. $oNewNode->SetProperty('developped', $oNode->GetProperty('developped'));
  463. break;
  464. default:
  465. $oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
  466. $oNewNode->SetProperty('label', $oNode->GetProperty('min_up'));
  467. $oNewNode->SetProperty('is_reached', true);
  468. }
  469. }
  470. $oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
  471. foreach($oEdgesIter as $oEdge)
  472. {
  473. $oSourceNode = $oNewGraph->GetNode($oEdge->GetSourceNode()->GetId());
  474. $oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId());
  475. $oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode);
  476. }
  477. // Remove duplicate edges between two nodes
  478. $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
  479. $aEdgeKeys = array();
  480. foreach($oEdgesIter as $oEdge)
  481. {
  482. $sSourceId = $oEdge->GetSourceNode()->GetId();
  483. $sSinkId = $oEdge->GetSinkNode()->GetId();
  484. if ($sSourceId == $sSinkId)
  485. {
  486. // Remove self referring edges
  487. $oNewGraph->_RemoveEdge($oEdge);
  488. }
  489. else
  490. {
  491. $sKey = $sSourceId.'//'.$sSinkId;
  492. if (array_key_exists($sKey, $aEdgeKeys))
  493. {
  494. // Remove duplicate edges
  495. $oNewGraph->_RemoveEdge($oEdge);
  496. }
  497. else
  498. {
  499. $aEdgeKeys[$sKey] = true;
  500. }
  501. }
  502. }
  503. $iNbGrouping = 1;
  504. //for($iter=0; $iter<$iNbGrouping; $iter++)
  505. {
  506. $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node');
  507. foreach($oNodesIter as $oNode)
  508. {
  509. if ($oNode->GetProperty('source'))
  510. {
  511. $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
  512. }
  513. }
  514. }
  515. // Remove duplicate edges between two nodes
  516. $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
  517. $aEdgeKeys = array();
  518. foreach($oEdgesIter as $oEdge)
  519. {
  520. $sSourceId = $oEdge->GetSourceNode()->GetId();
  521. $sSinkId = $oEdge->GetSinkNode()->GetId();
  522. if ($sSourceId == $sSinkId)
  523. {
  524. // Remove self referring edges
  525. $oNewGraph->_RemoveEdge($oEdge);
  526. }
  527. else
  528. {
  529. $sKey = $sSourceId.'//'.$sSinkId;
  530. if (array_key_exists($sKey, $aEdgeKeys))
  531. {
  532. // Remove duplicate edges
  533. $oNewGraph->_RemoveEdge($oEdge);
  534. }
  535. else
  536. {
  537. $aEdgeKeys[$sKey] = true;
  538. }
  539. }
  540. }
  541. return $oNewGraph;
  542. }
  543. public function InitOnGrid()
  544. {
  545. $iDist = 125;
  546. $aAllNodes = $this->_GetNodes();
  547. $iSide = ceil(sqrt(count($aAllNodes)));
  548. $xPos = 0;
  549. $yPos = 0;
  550. $idx = 0;
  551. foreach($aAllNodes as $oNode)
  552. {
  553. $xPos += $iDist;
  554. if (($idx % $iSide) == 0)
  555. {
  556. $xPos = 0;
  557. $yPos += $iDist;
  558. }
  559. $oNode->x = $xPos;
  560. $oNode->y = $yPos;
  561. $idx++;
  562. }
  563. }
  564. public function InitFromGraphviz()
  565. {
  566. $sDot = $this->DumpAsXDot();
  567. $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
  568. $aChunks = explode(";", $sDot);
  569. foreach($aChunks as $sChunk)
  570. {
  571. //echo "<p>$sChunk</p>";
  572. if(preg_match('/"([^"]+)".+pos="([0-9\\.]+),([0-9\\.]+)"/ms', $sChunk, $aMatches))
  573. {
  574. $sId = $aMatches[1];
  575. $xPos = $aMatches[2];
  576. $yPos = $aMatches[3];
  577. $oNode = $this->GetNode($sId);
  578. $oNode->x = (float)$xPos;
  579. $oNode->y = (float)$yPos;
  580. //echo "<p>$sId at $xPos,$yPos</p>";
  581. }
  582. else
  583. {
  584. //echo "<p>No match</p>";
  585. }
  586. }
  587. }
  588. public function BruteForceLayout($iNbTicks, $sDirection = 'horizontal')
  589. {
  590. $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
  591. $this->sDirection = $sDirection;
  592. $this->InitForces();
  593. for($i=0; $i<$iNbTicks; $i++)
  594. {
  595. set_time_limit($iLoopTimeLimit);
  596. $this->Tick();
  597. }
  598. }
  599. protected function InitForces()
  600. {
  601. $oIterator = new RelationTypeIterator($this, 'Node');
  602. $i = 0;
  603. foreach($oIterator as $sId => $oNode)
  604. {
  605. $oNode->SetProperty('ax', 0);
  606. $oNode->SetProperty('ay', 0);
  607. $oNode->SetProperty('vx', 0);
  608. $oNode->SetProperty('vy', 0);
  609. $i++;
  610. }
  611. }
  612. protected function ComputeAcceleration()
  613. {
  614. $oIterator = new RelationTypeIterator($this, 'Node');
  615. foreach($oIterator as $idx => $oNode)
  616. {
  617. $sNodeId = $oNode->GetId();
  618. $fx = 0;
  619. $fy = 0;
  620. $K = 0.6;
  621. $Q = 0.3;
  622. if ($oNode->GetProperty('source'))
  623. {
  624. switch($this->sDirection)
  625. {
  626. case 'horizontal':
  627. $fx -= 30;
  628. break;
  629. case 'vertical':
  630. $fy -= 30;
  631. break;
  632. default:
  633. // No gravity
  634. }
  635. }
  636. else
  637. {
  638. switch($this->sDirection)
  639. {
  640. case 'horizontal':
  641. $fx += 30;
  642. break;
  643. case 'vertical':
  644. $fy += 30;
  645. break;
  646. default:
  647. // No gravity
  648. }
  649. }
  650. //echo "<p>ComputeAcceleration - $sNodeId</p>\n";
  651. $oIter2 = new RelationTypeIterator($this, 'Edge');
  652. foreach($oIter2 as $sEdgeId => $oEdge)
  653. {
  654. $oSource = $oEdge->GetSourceNode();
  655. $oSink = $oEdge->GetSinkNode();
  656. //echo "<p>$sEdgeId ".$oSource->GetId()." -> ".$oSink->GetId()."</p>\n";
  657. if ($oSource->GetId() === $sNodeId)
  658. {
  659. $fx += -$K * ($oSource->x - $oSink->x);
  660. $fy += -$K * ($oSource->y - $oSink->y);
  661. //echo "<p>$sEdgeId Sink - F($fx, $fy)</p>\n";
  662. }
  663. else if ($oSink->GetId() === $sNodeId)
  664. {
  665. $fx += -$K * ($oSink->x - $oSource->x);
  666. $fy += -$K * ($oSink->y - $oSource->y);
  667. //echo "<p>$sEdgeId Source - F($fx, $fy)</p>\n";
  668. }
  669. // Else do nothing for this node, it's not connected via this edge
  670. }
  671. $oIter3 = new RelationTypeIterator($this, 'Node');
  672. foreach($oIter3 as $idx2 => $oOtherNode)
  673. {
  674. $sOtherId = $oOtherNode->GetId();
  675. if ($sOtherId !== $sNodeId)
  676. {
  677. $d2 = $oOtherNode->Distance2($oNode) / (60*60);
  678. if ($d2 < 15)
  679. {
  680. $dfx = -$Q * ($oOtherNode->x - $oNode->x) / $d2;
  681. $dfy = -$Q * ($oOtherNode->y - $oNode->y) / $d2;
  682. $fx += $dfx;
  683. $fy += $dfy;
  684. }
  685. //echo "<p>Electrostatic: $sOtherId d2: $d2 F($dfx, $dfy)</p>\n";
  686. }
  687. }
  688. //echo "<p>total forces: $sNodeId d2: $d2 F($fx, $fy)</p>\n";
  689. $oNode->SetProperty('ax', $fx);
  690. $oNode->SetProperty('ay', $fy);
  691. }
  692. }
  693. protected function Tick()
  694. {
  695. $dt = 0.1;
  696. $attenuation = 0.8;
  697. $M = 1;
  698. $this->ComputeAcceleration();
  699. $oIterator = new RelationTypeIterator($this, 'Node');
  700. foreach($oIterator as $sId => $oNode)
  701. {
  702. $vx = $attenuation * $oNode->GetProperty('vx') + $M * $oNode->GetProperty('ax');
  703. $vy = $attenuation * $oNode->GetProperty('vy') + $M * $oNode->GetProperty('ay');
  704. $oNode->x += $dt * $vx;
  705. $oNode->y += $dt * $vy;
  706. $oNode->SetProperty('vx', $vx);
  707. $oNode->SetProperty('vy', $vy);
  708. //echo "<p>$sId - V($vx, $vy)</p>\n";
  709. }
  710. }
  711. public function GetBoundingBox()
  712. {
  713. $xMin = null;
  714. $xMax = null;
  715. $yMin = null;
  716. $yMax = null;
  717. $oIterator = new RelationTypeIterator($this, 'Node');
  718. foreach($oIterator as $sId => $oNode)
  719. {
  720. if ($xMin === null) // First element in the loop
  721. {
  722. $xMin = $oNode->x - $oNode->GetWidth();
  723. $xMax = $oNode->x + $oNode->GetWidth();
  724. $yMin = $oNode->y - $oNode->GetHeight();
  725. $yMax = $oNode->y + $oNode->GetHeight();
  726. }
  727. else
  728. {
  729. $xMin = min($xMin, $oNode->x - $oNode->GetWidth() / 2);
  730. $xMax = max($xMax, $oNode->x + $oNode->GetWidth() / 2);
  731. $yMin = min($yMin, $oNode->y - $oNode->GetHeight() / 2);
  732. $yMax = max($yMax, $oNode->y + $oNode->GetHeight() / 2);
  733. }
  734. }
  735. return array('xmin' => $xMin, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
  736. }
  737. function Translate($dx, $dy)
  738. {
  739. $oIterator = new RelationTypeIterator($this, 'Node');
  740. foreach($oIterator as $sId => $oNode)
  741. {
  742. $oNode->x += $dx;
  743. $oNode->y += $dy;
  744. }
  745. }
  746. function RenderAsRaphael(WebPage $oP, $sId = null, $bContinue = false)
  747. {
  748. if ($sId == null)
  749. {
  750. $sId = 'graph';
  751. }
  752. $aBB = $this->GetBoundingBox();
  753. $oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
  754. $oP->add_ready_script("var oGraph = $('#$sId').simple_graph({xmin: {$aBB['xmin']}, xmax: {$aBB['xmax']}, ymin: {$aBB['ymin']}, ymax: {$aBB['ymax']} });");
  755. $oIterator = new RelationTypeIterator($this, 'Node');
  756. foreach($oIterator as $sId => $oNode)
  757. {
  758. $aNode = $oNode->GetForRaphael();
  759. $sJSNode = json_encode($aNode);
  760. $oP->add_ready_script("oGraph.simple_graph('add_node', $sJSNode);");
  761. }
  762. $oIterator = new RelationTypeIterator($this, 'Edge');
  763. foreach($oIterator as $sId => $oEdge)
  764. {
  765. $aEdge = array();
  766. $aEdge['id'] = $oEdge->GetId();
  767. $aEdge['source_node_id'] = $oEdge->GetSourceNode()->GetId();
  768. $aEdge['sink_node_id'] = $oEdge->GetSinkNode()->GetId();
  769. $fOpacity = ($oEdge->GetSinkNode()->GetProperty('is_reached') && $oEdge->GetSourceNode()->GetProperty('is_reached') ? 1 : 0.2);
  770. $aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000');
  771. $sJSEdge = json_encode($aEdge);
  772. $oP->add_ready_script("oGraph.simple_graph('add_edge', $sJSEdge);");
  773. }
  774. $oP->add_ready_script("oGraph.simple_graph('draw');");
  775. }
  776. function RenderAsPDF(WebPage $oP, $sTitle = 'Untitled', $sPageFormat = 'A4', $sPageOrientation = 'P')
  777. {
  778. require_once(APPROOT.'lib/tcpdf/tcpdf.php');
  779. $oPdf = new TCPDF($sPageOrientation, 'mm', $sPageFormat, true, 'UTF-8', false);
  780. // set document information
  781. $oPdf->SetCreator(PDF_CREATOR);
  782. $oPdf->SetAuthor('iTop');
  783. $oPdf->SetTitle($sTitle);
  784. $oPdf->setFontSubsetting(true);
  785. // Set font
  786. // dejavusans is a UTF-8 Unicode font, if you only need to
  787. // print standard ASCII chars, you can use core fonts like
  788. // helvetica or times to reduce file size.
  789. $oPdf->SetFont('dejavusans', '', 14, '', true);
  790. // set auto page breaks
  791. $oPdf->SetAutoPageBreak(false);
  792. // Add a page
  793. // This method has several options, check the source code documentation for more information.
  794. $oPdf->AddPage();
  795. $aBB = $this->GetBoundingBox();
  796. //$this->Translate(-$aBB['xmin'], -$aBB['ymin']);
  797. if ($sPageOrientation == 'P')
  798. {
  799. // Portrait mode
  800. $fHMargin = 10; // mm
  801. $fVMargin = 15; // mm
  802. }
  803. else
  804. {
  805. // Landscape mode
  806. $fHMargin = 15; // mm
  807. $fVMargin = 10; // mm
  808. }
  809. $fPageW = $oPdf->getPageWidth() - 2 * $fHMargin;
  810. $fPageH = $oPdf->getPageHeight() - 2 * $fVMargin;
  811. $w = $aBB['xmax'] - $aBB['xmin'];
  812. $h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons
  813. $fScale = min($fPageW / $w, $fPageH / $h);
  814. $dx = ($fPageW - $fScale * $w) / 2;
  815. $dy = ($fPageH - $fScale * $h) / 2;
  816. $this->Translate(($fHMargin + $dx)/$fScale, ($fVMargin + $dy)/$fScale);
  817. $oIterator = new RelationTypeIterator($this, 'Edge');
  818. foreach($oIterator as $sId => $oEdge)
  819. {
  820. $oEdge->RenderAsPDF($oPdf, $fScale);
  821. }
  822. $oIterator = new RelationTypeIterator($this, 'Node');
  823. foreach($oIterator as $sId => $oNode)
  824. {
  825. $oNode->RenderAsPDF($oPdf, $fScale);
  826. }
  827. $oP->add($oPdf->Output('iTop.pdf', 'S'));
  828. }
  829. }