displayablegraph.class.inc.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  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['obj_class'] = get_class($this->GetProperty('object'));
  81. $aNode['obj_key'] = $this->GetProperty('object')->GetKey();
  82. $aNode['sink'] = ($this->GetProperty('sink') == true);
  83. $aNode['x'] = $this->x;
  84. $aNode['y']= $this->y;
  85. $aNode['label'] = $this->GetLabel();
  86. $aNode['id'] = $this->GetId();
  87. $fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  88. $aNode['icon_attr'] = array('opacity' => $fOpacity);
  89. $aNode['text_attr'] = array('opacity' => $fOpacity);
  90. return $aNode;
  91. }
  92. public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale)
  93. {
  94. $Alpha = 1.0;
  95. $oPdf->SetFillColor(200, 200, 200);
  96. $oPdf->setAlpha(1);
  97. $sIconUrl = $this->GetProperty('icon_url');
  98. $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
  99. if ($this->GetProperty('source'))
  100. {
  101. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
  102. $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
  103. }
  104. else if ($this->GetProperty('sink'))
  105. {
  106. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
  107. $oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
  108. }
  109. if (!$this->GetProperty('is_reached'))
  110. {
  111. $sTempImageName = $this->CreateWhiteIcon($oGraph, $sIconPath);
  112. if ($sTempImageName != null)
  113. {
  114. $oPdf->Image($sTempImageName, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale, 'PNG');
  115. }
  116. $Alpha = 0.4;
  117. $oPdf->setAlpha($Alpha);
  118. }
  119. $oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
  120. $oPdf->SetFont('dejavusans', '', 24 * $fScale, '', true);
  121. $width = $oPdf->GetStringWidth($this->GetProperty('label'));
  122. $height = $oPdf->GetStringHeight(1000, $this->GetProperty('label'));
  123. $oPdf->setAlpha(0.6 * $Alpha);
  124. $oPdf->SetFillColor(255, 255, 255);
  125. $oPdf->SetDrawColor(255, 255, 255);
  126. $oPdf->Rect($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $width, $height, 'DF');
  127. $oPdf->setAlpha($Alpha);
  128. $oPdf->SetTextColor(0, 0, 0);
  129. $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $this->GetProperty('label'));
  130. }
  131. /**
  132. * Create a "whitened" version of the icon (retaining the transparency) to be used a background for masking the underlying lines
  133. * @param string $sIconFile The path to the file containing the icon
  134. * @return NULL|string The path to a temporary file containing the white version of the icon
  135. */
  136. protected function CreateWhiteIcon(DisplayableGraph $oGraph, $sIconFile)
  137. {
  138. $aInfo = getimagesize($sIconFile);
  139. $im = null;
  140. switch($aInfo['mime'])
  141. {
  142. case 'image/png':
  143. if (function_exists('imagecreatefrompng'))
  144. {
  145. $im = imagecreatefrompng($sIconFile);
  146. }
  147. break;
  148. case 'image/gif':
  149. if (function_exists('imagecreatefromgif'))
  150. {
  151. $im = imagecreatefromgif($sIconFile);
  152. }
  153. break;
  154. case 'image/jpeg':
  155. case 'image/jpg':
  156. if (function_exists('imagecreatefromjpeg'))
  157. {
  158. $im = imagecreatefromjpeg($sIconFile);
  159. }
  160. break;
  161. default:
  162. return null;
  163. }
  164. if($im && imagefilter($im, IMG_FILTER_COLORIZE, 255, 255, 255))
  165. {
  166. $sTempImageName = $oGraph->GetTempImageName();
  167. imagesavealpha($im, true);
  168. imagepng($im, $sTempImageName);
  169. imagedestroy($im);
  170. return $sTempImageName;
  171. }
  172. else
  173. {
  174. return null;
  175. }
  176. }
  177. /**
  178. * Group together (as a special kind of nodes) all the similar neighbours of the current node
  179. * @param DisplayableGraph $oGraph
  180. * @param int $iThresholdCount
  181. * @param boolean $bDirectionUp
  182. * @param boolean $bDirectionDown
  183. */
  184. public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
  185. {
  186. //echo "<p>".$this->GetProperty('label').":</p>";
  187. if ($this->GetProperty('grouped') === true) return;
  188. $this->SetProperty('grouped', true);
  189. if ($bDirectionDown)
  190. {
  191. $aNodesPerClass = array();
  192. foreach($this->GetOutgoingEdges() as $oEdge)
  193. {
  194. $oNode = $oEdge->GetSinkNode();
  195. if ($oNode->GetProperty('class') !== null)
  196. {
  197. $sClass = $oNode->GetProperty('class');
  198. if (($sClass!== null) && (!array_key_exists($sClass, $aNodesPerClass)))
  199. {
  200. $aNodesPerClass[$sClass] = array(
  201. 'reached' => array(
  202. 'count' => 0,
  203. 'nodes' => array(),
  204. 'icon_url' => $oNode->GetProperty('icon_url'),
  205. ),
  206. 'not_reached' => array(
  207. 'count' => 0,
  208. 'nodes' => array(),
  209. 'icon_url' => $oNode->GetProperty('icon_url'),
  210. )
  211. );
  212. }
  213. $sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
  214. if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
  215. {
  216. $aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
  217. $aNodesPerClass[$sClass][$sKey]['count'] += (int)$oNode->GetProperty('count', 1);
  218. //echo "<p>New count: ".$aNodesPerClass[$sClass][$sKey]['count']."</p>";
  219. }
  220. }
  221. else
  222. {
  223. $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  224. }
  225. }
  226. foreach($aNodesPerClass as $sClass => $aDefs)
  227. {
  228. foreach($aDefs as $sStatus => $aGroupProps)
  229. {
  230. //echo "<p>$sClass/$sStatus: {$aGroupProps['count']} object(s), actually: ".count($aGroupProps['nodes'])."</p>";
  231. if (count($aGroupProps['nodes']) >= $iThresholdCount)
  232. {
  233. $oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.$sClass);
  234. $oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
  235. $oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
  236. $oNewNode->SetProperty('class', $sClass);
  237. $oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
  238. $oNewNode->SetProperty('count', $aGroupProps['count']);
  239. //$oNewNode->SetProperty('grouped', true);
  240. $oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
  241. foreach($aGroupProps['nodes'] as $oNode)
  242. {
  243. foreach($oNode->GetIncomingEdges() as $oEdge)
  244. {
  245. if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
  246. {
  247. $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
  248. }
  249. }
  250. foreach($oNode->GetOutgoingEdges() as $oEdge)
  251. {
  252. $aOutgoing[] = $oEdge->GetSinkNode();
  253. try
  254. {
  255. $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
  256. }
  257. catch(Exception $e)
  258. {
  259. // ignore this edge
  260. }
  261. }
  262. if ($oGraph->GetNode($oNode->GetId()))
  263. {
  264. $oGraph->_RemoveNode($oNode);
  265. $oNewNode->AddObject($oNode->GetProperty('object'));
  266. }
  267. }
  268. $oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  269. }
  270. else
  271. {
  272. foreach($aGroupProps['nodes'] as $oNode)
  273. {
  274. $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  275. }
  276. }
  277. }
  278. }
  279. }
  280. }
  281. }
  282. class DisplayableRedundancyNode extends DisplayableNode
  283. {
  284. public function GetWidth()
  285. {
  286. return 24;
  287. }
  288. public function GetForRaphael()
  289. {
  290. $aNode = array();
  291. $aNode['shape'] = 'disc';
  292. $aNode['icon_url'] = $this->GetIconURL();
  293. $aNode['source'] = ($this->GetProperty('source') == true);
  294. $aNode['width'] = $this->GetWidth();
  295. $aNode['x'] = $this->x;
  296. $aNode['y']= $this->y;
  297. $aNode['label'] = $this->GetLabel();
  298. $aNode['id'] = $this->GetId();
  299. $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
  300. $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#c33', 'opacity' => $fDiscOpacity);
  301. $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  302. $aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity);
  303. return $aNode;
  304. }
  305. public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale)
  306. {
  307. $oPdf->SetAlpha(1);
  308. $oPdf->SetFillColor(200, 0, 0);
  309. $oPdf->SetDrawColor(0, 0, 0);
  310. $oPdf->Circle($this->x*$fScale, $this->y*$fScale, 16*$fScale, 0, 360, 'DF');
  311. $oPdf->SetTextColor(255, 255, 255);
  312. $oPdf->SetFont('dejavusans', '', 28 * $fScale, '', true);
  313. $sLabel = (string)$this->GetProperty('label');
  314. $width = $oPdf->GetStringWidth($sLabel, 'dejavusans', 'B', 24*$fScale);
  315. $height = $oPdf->GetStringHeight(1000, $sLabel);
  316. $xPos = (float)$this->x*$fScale - $width/2;
  317. $yPos = (float)$this->y*$fScale - $height/2;
  318. $oPdf->SetXY(($this->x - 16)*$fScale, ($this->y - 16)*$fScale);
  319. $oPdf->Cell(32*$fScale, 32*$fScale, $sLabel, 0, 0, 'C', 0, '', 0, false, 'T', 'C');
  320. }
  321. /**
  322. * @see DisplayableNode::GroupSimilarNeighbours()
  323. */
  324. public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
  325. {
  326. parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  327. if ($bDirectionUp)
  328. {
  329. $aNodesPerClass = array();
  330. foreach($this->GetIncomingEdges() as $oEdge)
  331. {
  332. $oNode = $oEdge->GetSourceNode();
  333. if (($oNode->GetProperty('class') !== null) && (!$oNode->GetProperty('is_reached')))
  334. {
  335. $sClass = $oNode->GetProperty('class');
  336. if (!array_key_exists($sClass, $aNodesPerClass))
  337. {
  338. $aNodesPerClass[$sClass] = array('reached' => array(), 'not_reached' => array());
  339. }
  340. $aNodesPerClass[$sClass][$oNode->GetProperty('is_reached') ? 'reached' : 'not_reached'][] = $oNode;
  341. }
  342. else
  343. {
  344. //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  345. }
  346. }
  347. foreach($aNodesPerClass as $sClass => $aDefs)
  348. {
  349. foreach($aDefs as $sStatus => $aNodes)
  350. {
  351. //echo "<p>".$this->GetId().' has '.count($aNodes)." neighbours of class $sClass in status $sStatus\n";
  352. if (count($aNodes) >= $iThresholdCount)
  353. {
  354. $oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
  355. $oNewNode->SetProperty('label', 'x'.count($aNodes));
  356. $oNewNode->SetProperty('icon_url', $aNodes[0]->GetProperty('icon_url'));
  357. $oNewNode->SetProperty('is_reached', $aNodes[0]->GetProperty('is_reached'));
  358. $oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
  359. foreach($aNodes as $oNode)
  360. {
  361. foreach($oNode->GetIncomingEdges() as $oEdge)
  362. {
  363. $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
  364. }
  365. foreach($oNode->GetOutgoingEdges() as $oEdge)
  366. {
  367. if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
  368. {
  369. $aOutgoing[] = $oEdge->GetSinkNode();
  370. $oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
  371. }
  372. }
  373. //echo "<p>Replacing ".$oNode->GetId().' by '.$oNewNode->GetId()."\n";
  374. $oGraph->_RemoveNode($oNode);
  375. $oNewNode->AddObject($oNode->GetProperty('object'));
  376. }
  377. //$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  378. }
  379. else
  380. {
  381. foreach($aNodes as $oNode)
  382. {
  383. //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
  384. }
  385. }
  386. }
  387. }
  388. }
  389. }
  390. }
  391. class DisplayableEdge extends GraphEdge
  392. {
  393. public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale)
  394. {
  395. $xStart = $this->GetSourceNode()->x * $fScale;
  396. $yStart = $this->GetSourceNode()->y * $fScale;
  397. $xEnd = $this->GetSinkNode()->x * $fScale;
  398. $yEnd = $this->GetSinkNode()->y * $fScale;
  399. $bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached'));
  400. $oPdf->setAlpha(1);
  401. if ($bReached)
  402. {
  403. $aColor = array(100, 100, 100);
  404. }
  405. else
  406. {
  407. $aColor = array(200, 200, 200);
  408. }
  409. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aColor));
  410. $oPdf->Line($xStart, $yStart, $xEnd, $yEnd);
  411. $vx = $xEnd - $xStart;
  412. $vy = $yEnd - $yStart;
  413. $l = sqrt($vx*$vx + $vy*$vy);
  414. $vx = $vx / $l;
  415. $vy = $vy / $l;
  416. $ux = -$vy;
  417. $uy = $vx;
  418. $lPos = max($l/2, $l - 40*$fScale);
  419. $iArrowSize = 5*$fScale;
  420. $x = $xStart + $lPos * $vx;
  421. $y = $yStart + $lPos * $vy;
  422. $oPdf->Line($x, $y, $x + $iArrowSize * ($ux-$vx), $y + $iArrowSize * ($uy-$vy));
  423. $oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy));
  424. }
  425. }
  426. class DisplayableGroupNode extends DisplayableNode
  427. {
  428. protected $aObjects;
  429. public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
  430. {
  431. parent::__construct($oGraph, $sId, $x, $y);
  432. $this->aObjects = array();
  433. }
  434. public function AddObject(DBObject $oObj)
  435. {
  436. $this->aObjects[$oObj->GetKey()] = $oObj;
  437. }
  438. public function GetObjects()
  439. {
  440. return $this->aObjects;
  441. }
  442. public function GetWidth()
  443. {
  444. return 50;
  445. }
  446. public function GetForRaphael()
  447. {
  448. $aNode = array();
  449. $aNode['shape'] = 'group';
  450. $aNode['icon_url'] = $this->GetIconURL();
  451. $aNode['source'] = ($this->GetProperty('source') == true);
  452. $aNode['width'] = $this->GetWidth();
  453. $aNode['x'] = $this->x;
  454. $aNode['y']= $this->y;
  455. $aNode['label'] = $this->GetLabel();
  456. $aNode['id'] = $this->GetId();
  457. $aNode['group_index'] = $this->GetProperty('group_index'); // if supplied
  458. $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2);
  459. $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4);
  460. $aNode['icon_attr'] = array('opacity' => $fTextOpacity);
  461. $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#fff', 'opacity' => $fDiscOpacity);
  462. $aNode['text_attr'] = array('fill' => '#000', 'opacity' => $fTextOpacity);
  463. return $aNode;
  464. }
  465. public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale)
  466. {
  467. $bReached = $this->GetProperty('is_reached');
  468. $oPdf->SetFillColor(255, 255, 255);
  469. if ($bReached)
  470. {
  471. $aBorderColor = array(100, 100, 100);
  472. }
  473. else
  474. {
  475. $aBorderColor = array(200, 200, 200);
  476. }
  477. $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
  478. $sIconUrl = $this->GetProperty('icon_url');
  479. $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
  480. $oPdf->SetAlpha(1);
  481. $oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
  482. if ($bReached)
  483. {
  484. $oPdf->SetAlpha(1);
  485. }
  486. else
  487. {
  488. $oPdf->SetAlpha(0.4);
  489. }
  490. $oPdf->Image($sIconPath, ($this->x - 17)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
  491. $oPdf->Image($sIconPath, ($this->x + 1)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
  492. $oPdf->Image($sIconPath, ($this->x -8)*$fScale, ($this->y +1)*$fScale, 16*$fScale, 16*$fScale);
  493. $oPdf->SetFont('dejavusans', '', 24 * $fScale, '', true);
  494. $width = $oPdf->GetStringWidth($this->GetProperty('label'));
  495. $oPdf->SetTextColor(0, 0, 0);
  496. $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label'));
  497. }
  498. }
  499. /**
  500. * A Graph that can be displayed interactively using Raphael JS or saved as a PDF document
  501. */
  502. class DisplayableGraph extends SimpleGraph
  503. {
  504. protected $bDirectionDown;
  505. protected $aTempImages;
  506. protected $aSourceObjects;
  507. protected $aSinkObjects;
  508. public function __construct()
  509. {
  510. parent::__construct();
  511. $this->aTempImages = array();
  512. $this->aSourceObjects = array();
  513. $this->aSinkObjects = array();
  514. }
  515. public function GetTempImageName()
  516. {
  517. $sNewTempName = tempnam(APPROOT.'data', 'img-');
  518. $this->aTempImages[] = $sNewTempName;
  519. return $sNewTempName;
  520. }
  521. public function __destruct()
  522. {
  523. foreach($this->aTempImages as $sTempFile)
  524. {
  525. @unlink($sTempFile);
  526. }
  527. }
  528. /**
  529. * Build a DisplayableGraph from a RelationGraph
  530. * @param RelationGraph $oGraph
  531. * @param number $iGroupingThreshold
  532. * @param string $bDirectionDown
  533. * @return DisplayableGraph
  534. */
  535. public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true)
  536. {
  537. $oNewGraph = new DisplayableGraph();
  538. $oNewGraph->bDirectionDown = $bDirectionDown;
  539. $oNodesIter = new RelationTypeIterator($oGraph, 'Node');
  540. foreach($oNodesIter as $oNode)
  541. {
  542. switch(get_class($oNode))
  543. {
  544. case 'RelationObjectNode':
  545. $oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
  546. $oObj = $oNode->GetProperty('object');
  547. $sClass = get_class($oObj);
  548. if ($oNode->GetProperty('source'))
  549. {
  550. if (!array_key_exists($sClass, $oNewGraph->aSourceObjects))
  551. {
  552. $oNewGraph->aSourceObjects[$sClass] = array();
  553. }
  554. $oNewGraph->aSourceObjects[$sClass][] = $oObj->GetKey();
  555. $oNewNode->SetProperty('source', true);
  556. }
  557. if ($oNode->GetProperty('sink'))
  558. {
  559. if (!array_key_exists($sClass, $oNewGraph->aSinkObjects))
  560. {
  561. $oNewGraph->aSinkObjects[$sClass] = array();
  562. }
  563. $oNewGraph->aSinkObjects[$sClass][] = $oObj->GetKey();
  564. $oNewNode->SetProperty('sink', true);
  565. }
  566. $oNewNode->SetProperty('class', $sClass);
  567. $oNewNode->SetProperty('object', $oObj);
  568. $oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
  569. $oNewNode->SetProperty('label', $oObj->GetRawName());
  570. $oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
  571. $oNewNode->SetProperty('developped', $oNode->GetProperty('developped'));
  572. break;
  573. default:
  574. $oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
  575. $oNewNode->SetProperty('label', $oNode->GetProperty('min_up'));
  576. $oNewNode->SetProperty('is_reached', true);
  577. }
  578. }
  579. $oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
  580. foreach($oEdgesIter as $oEdge)
  581. {
  582. $oSourceNode = $oNewGraph->GetNode($oEdge->GetSourceNode()->GetId());
  583. $oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId());
  584. $oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode);
  585. }
  586. // Remove duplicate edges between two nodes
  587. $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
  588. $aEdgeKeys = array();
  589. foreach($oEdgesIter as $oEdge)
  590. {
  591. $sSourceId = $oEdge->GetSourceNode()->GetId();
  592. $sSinkId = $oEdge->GetSinkNode()->GetId();
  593. if ($sSourceId == $sSinkId)
  594. {
  595. // Remove self referring edges
  596. $oNewGraph->_RemoveEdge($oEdge);
  597. }
  598. else
  599. {
  600. $sKey = $sSourceId.'//'.$sSinkId;
  601. if (array_key_exists($sKey, $aEdgeKeys))
  602. {
  603. // Remove duplicate edges
  604. $oNewGraph->_RemoveEdge($oEdge);
  605. }
  606. else
  607. {
  608. $aEdgeKeys[$sKey] = true;
  609. }
  610. }
  611. }
  612. $iNbGrouping = 1;
  613. //for($iter=0; $iter<$iNbGrouping; $iter++)
  614. {
  615. $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node');
  616. foreach($oNodesIter as $oNode)
  617. {
  618. if ($oNode->GetProperty('source'))
  619. {
  620. $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
  621. }
  622. }
  623. }
  624. // Remove duplicate edges between two nodes
  625. $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge');
  626. $aEdgeKeys = array();
  627. foreach($oEdgesIter as $oEdge)
  628. {
  629. $sSourceId = $oEdge->GetSourceNode()->GetId();
  630. $sSinkId = $oEdge->GetSinkNode()->GetId();
  631. if ($sSourceId == $sSinkId)
  632. {
  633. // Remove self referring edges
  634. $oNewGraph->_RemoveEdge($oEdge);
  635. }
  636. else
  637. {
  638. $sKey = $sSourceId.'//'.$sSinkId;
  639. if (array_key_exists($sKey, $aEdgeKeys))
  640. {
  641. // Remove duplicate edges
  642. $oNewGraph->_RemoveEdge($oEdge);
  643. }
  644. else
  645. {
  646. $aEdgeKeys[$sKey] = true;
  647. }
  648. }
  649. }
  650. return $oNewGraph;
  651. }
  652. /**
  653. * Initializes the positions by rendering using Graphviz in xdot format
  654. * and parsing the output.
  655. * @throws Exception
  656. */
  657. public function InitFromGraphviz()
  658. {
  659. $sDot = $this->DumpAsXDot();
  660. if (strpos($sDot, 'digraph') === false)
  661. {
  662. throw new Exception($sDot);
  663. }
  664. $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
  665. $aChunks = explode(";", $sDot);
  666. foreach($aChunks as $sChunk)
  667. {
  668. //echo "<p>$sChunk</p>";
  669. if(preg_match('/"([^"]+)".+pos="([0-9\\.]+),([0-9\\.]+)"/ms', $sChunk, $aMatches))
  670. {
  671. $sId = $aMatches[1];
  672. $xPos = $aMatches[2];
  673. $yPos = $aMatches[3];
  674. $oNode = $this->GetNode($sId);
  675. $oNode->x = (float)$xPos;
  676. $oNode->y = (float)$yPos;
  677. //echo "<p>$sId at $xPos,$yPos</p>";
  678. }
  679. else
  680. {
  681. //echo "<p>No match</p>";
  682. }
  683. }
  684. }
  685. public function GetBoundingBox()
  686. {
  687. $xMin = null;
  688. $xMax = null;
  689. $yMin = null;
  690. $yMax = null;
  691. $oIterator = new RelationTypeIterator($this, 'Node');
  692. foreach($oIterator as $sId => $oNode)
  693. {
  694. if ($xMin === null) // First element in the loop
  695. {
  696. $xMin = $oNode->x - $oNode->GetWidth();
  697. $xMax = $oNode->x + $oNode->GetWidth();
  698. $yMin = $oNode->y - $oNode->GetHeight();
  699. $yMax = $oNode->y + $oNode->GetHeight();
  700. }
  701. else
  702. {
  703. $xMin = min($xMin, $oNode->x - $oNode->GetWidth() / 2);
  704. $xMax = max($xMax, $oNode->x + $oNode->GetWidth() / 2);
  705. $yMin = min($yMin, $oNode->y - $oNode->GetHeight() / 2);
  706. $yMax = max($yMax, $oNode->y + $oNode->GetHeight() / 2);
  707. }
  708. }
  709. return array('xmin' => $xMin, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
  710. }
  711. function Translate($dx, $dy)
  712. {
  713. $oIterator = new RelationTypeIterator($this, 'Node');
  714. foreach($oIterator as $sId => $oNode)
  715. {
  716. $oNode->x += $dx;
  717. $oNode->y += $dy;
  718. }
  719. }
  720. public function UpdatePositions($aPositions)
  721. {
  722. foreach($aPositions as $sNodeId => $aPos)
  723. {
  724. $oNode = $this->GetNode($sNodeId);
  725. if ($oNode != null)
  726. {
  727. $oNode->x = $aPos['x'];
  728. $oNode->y = $aPos['y'];
  729. }
  730. }
  731. }
  732. /**
  733. * Renders as JSON string suitable for loading into the simple_graph widget
  734. */
  735. function GetAsJSON()
  736. {
  737. $aData = array('nodes' => array(), 'edges' => array());
  738. $iGroupIdx = 0;
  739. $oIterator = new RelationTypeIterator($this, 'Node');
  740. foreach($oIterator as $sId => $oNode)
  741. {
  742. if ($oNode instanceof DisplayableGroupNode)
  743. {
  744. $aGroups[] = $oNode->GetObjects();
  745. $oNode->SetProperty('group_index', $iGroupIdx);
  746. $iGroupIdx++;
  747. }
  748. $aData['nodes'][] = $oNode->GetForRaphael();
  749. }
  750. $oIterator = new RelationTypeIterator($this, 'Edge');
  751. foreach($oIterator as $sId => $oEdge)
  752. {
  753. $aEdge = array();
  754. $aEdge['id'] = $oEdge->GetId();
  755. $aEdge['source_node_id'] = $oEdge->GetSourceNode()->GetId();
  756. $aEdge['sink_node_id'] = $oEdge->GetSinkNode()->GetId();
  757. $fOpacity = ($oEdge->GetSinkNode()->GetProperty('is_reached') && $oEdge->GetSourceNode()->GetProperty('is_reached') ? 1 : 0.2);
  758. $aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000');
  759. $aData['edges'][] = $aEdge;
  760. }
  761. return json_encode($aData);
  762. }
  763. /**
  764. * Renders the graph in a PDF document: centered in the current page
  765. * @param PDFPage $oPage The PDFPage representing the PDF document to draw into
  766. * @param string $sComments An optional comment to display next to the graph (HTML entities will be escaped, \n replaced by <br/>)
  767. * @param float $xMin Left coordinate of the bounding box to display the graph
  768. * @param float $xMax Right coordinate of the bounding box to display the graph
  769. * @param float $yMin Top coordinate of the bounding box to display the graph
  770. * @param float $yMax Bottom coordinate of the bounding box to display the graph
  771. */
  772. function RenderAsPDF(PDFPage $oPage, $sComments = '', $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1)
  773. {
  774. $oPdf = $oPage->get_tcpdf();
  775. $aBB = $this->GetBoundingBox();
  776. $this->Translate(-$aBB['xmin'], -$aBB['ymin']);
  777. $aMargins = $oPdf->getMargins();
  778. if ($xMin == -1)
  779. {
  780. $xMin = $aMargins['left'];
  781. }
  782. if ($xMax == -1)
  783. {
  784. $xMax = $oPdf->getPageWidth() - $aMargins['right'];
  785. }
  786. if ($yMin == -1)
  787. {
  788. $yMin = $aMargins['top'];
  789. }
  790. if ($yMax == -1)
  791. {
  792. $yMax = $oPdf->getPageHeight() - $aMargins['bottom'];
  793. }
  794. $fBreakMargin = $oPdf->getBreakMargin();
  795. $oPdf->SetAutoPageBreak(false);
  796. $fKeyWidth = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax);
  797. $xMin += + $fKeyWidth;
  798. //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 225, 225));
  799. $fPageW = $xMax - $xMin;
  800. $fPageH = $yMax - $yMin;
  801. $w = $aBB['xmax'] - $aBB['xmin'];
  802. $h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons
  803. $fScale = min($fPageW / $w, $fPageH / $h);
  804. $dx = ($fPageW - $fScale * $w) / 2;
  805. $dy = ($fPageH - $fScale * $h) / 2;
  806. $this->Translate(($xMin + $dx)/$fScale, ($yMin + $dy)/$fScale);
  807. $oIterator = new RelationTypeIterator($this, 'Edge');
  808. $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
  809. foreach($oIterator as $sId => $oEdge)
  810. {
  811. set_time_limit($iLoopTimeLimit);
  812. $oEdge->RenderAsPDF($oPdf, $this, $fScale);
  813. }
  814. $oIterator = new RelationTypeIterator($this, 'Node');
  815. foreach($oIterator as $sId => $oNode)
  816. {
  817. set_time_limit($iLoopTimeLimit);
  818. $oNode->RenderAsPDF($oPdf, $this, $fScale);
  819. }
  820. $oIterator = new RelationTypeIterator($this, 'Node');
  821. $oPdf->SetAutoPageBreak(true, $fBreakMargin);
  822. $oPdf->SetAlpha(1);
  823. }
  824. /**
  825. * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax). Returns the width used by the legend.
  826. * @param TCPDF $oPdf
  827. * @param string $sComments
  828. * @param float $xMin
  829. * @param float $yMin
  830. * @param float $xMax
  831. * @param float $yMax
  832. * @return number The width used by the legend
  833. */
  834. protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax)
  835. {
  836. $fFontSize = 7; // in mm
  837. $fIconSize = 6; // in mm
  838. $fPadding = 1; // in mm
  839. $oIterator = new RelationTypeIterator($this, 'Node');
  840. $fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - $fIconSize, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - $fIconSize);
  841. $aClasses = array();
  842. $aIcons = array();
  843. $oPdf->SetFont('dejavusans', '', $fFontSize, '', true);
  844. foreach($oIterator as $sId => $oNode)
  845. {
  846. if ($sClass = $oNode->GetProperty('class'))
  847. {
  848. if (!array_key_exists($sClass, $aClasses))
  849. {
  850. $sClassLabel = MetaModel::GetName($sClass);
  851. $width = $oPdf->GetStringWidth($sClassLabel);
  852. $fMaxWidth = max($width, $fMaxWidth);
  853. $aClasses[$sClass] = $sClassLabel;
  854. $sIconUrl = $oNode->GetProperty('icon_url');
  855. $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
  856. $aIcons[$sClass] = $sIconPath;
  857. }
  858. }
  859. }
  860. $oPdf->SetXY($xMin + $fPadding, $yMin + $fPadding);
  861. $yPos = $yMin + $fPadding;
  862. $oPdf->SetFillColor(225, 225, 225);
  863. $oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
  864. $yPos += $fIconSize + 2*$fPadding;
  865. foreach($aClasses as $sClass => $sLabel)
  866. {
  867. $oPdf->SetX($xMin + $fIconSize + $fPadding);
  868. $oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
  869. $oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize);
  870. $yPos += $fIconSize + 2*$fPadding;
  871. }
  872. $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yPos - $yMin, 'D');
  873. $oPdf->Rect($xMin, $yPos, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yPos, 'D');
  874. $yPos +=1;
  875. $oPdf->SetXY($xMin + $fPadding, $yPos);
  876. $oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
  877. $yPos += $fIconSize + 2*$fPadding;
  878. $oPdf->SetX($xMin);
  879. $oPdf->writeHTMLCell($fIconSize + 2*$fPadding + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'</p>', 0 /* border */, 1 /* ln */);
  880. return $fMaxWidth + $fIconSize + 4*$fPadding;
  881. }
  882. /**
  883. * Display the graph inside the given page, with the "filter" drawer above it
  884. * @param WebPage $oP
  885. * @param hash $aResults
  886. * @param string $sRelation
  887. * @param ApplicationContext $oAppContext
  888. * @param array $aExcludedObjects
  889. */
  890. function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects)
  891. {
  892. $aExcludedByClass = array();
  893. foreach($aExcludedObjects as $oObj)
  894. {
  895. if (!array_key_exists(get_class($oObj), $aExcludedByClass))
  896. {
  897. $aExcludedByClass[get_class($oObj)] = array();
  898. }
  899. $aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
  900. }
  901. $oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
  902. $oP->add_ready_script(
  903. <<<EOF
  904. $( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
  905. $("#dh_flash").click( function() {
  906. $("#ds_flash").slideToggle('normal', function() { $("#ds_flash").parent().resize(); } );
  907. $("#dh_flash").toggleClass('open');
  908. });
  909. $('#ReloadMovieBtn').button().button('disable');
  910. EOF
  911. );
  912. $aSortedElements = array();
  913. foreach($aResults as $sClassIdx => $aObjects)
  914. {
  915. foreach($aObjects as $oCurrObj)
  916. {
  917. $sSubClass = get_class($oCurrObj);
  918. $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass);
  919. }
  920. }
  921. asort($aSortedElements);
  922. $idx = 0;
  923. foreach($aSortedElements as $sSubClass => $sClassName)
  924. {
  925. $oP->add("<span style=\"padding-right:2em; white-space:nowrap;\"><input type=\"checkbox\" id=\"exclude_$idx\" name=\"excluded[]\" value=\"$sSubClass\" checked onChange=\"$('#ReloadMovieBtn').button('enable')\"><label for=\"exclude_$idx\">&nbsp;".MetaModel::GetClassIcon($sSubClass)."&nbsp;$sClassName</label></span> ");
  926. $idx++;
  927. }
  928. $oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"DoReload()\">".Dict::S('UI:Button:Refresh')."</button></p>");
  929. $oP->add("</div>\n");
  930. $oP->add("<div class=\"HRDrawer\"></div>\n");
  931. $oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">".Dict::S('UI:ElementsDisplayed')."</div>\n");
  932. $sDirection = utils::ReadParam('d', 'horizontal');
  933. $iGroupingThreshold = utils::ReadParam('g', 5);
  934. $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
  935. $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
  936. $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
  937. $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
  938. try
  939. {
  940. $this->InitFromGraphviz();
  941. $sExportAsPdfURL = '';
  942. if (extension_loaded('gd'))
  943. {
  944. $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up');
  945. }
  946. $oAppcontext = new ApplicationContext();
  947. $sContext = $oAppContext->GetForLink();
  948. $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext;
  949. $sExportAsDocumentURL = '';
  950. $sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up');
  951. $sId = 'graph';
  952. $oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
  953. $aParams = array(
  954. 'source_url' => $sLoadFromURL,
  955. 'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects),
  956. 'excluded' => $aExcludedByClass,
  957. 'grouping_threshold' => $iGroupingThreshold,
  958. 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')),
  959. //'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')),
  960. 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')),
  961. 'labels' => array(
  962. 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'),
  963. 'export' => Dict::S('UI:Button:Export'),
  964. 'cancel' => Dict::S('UI:Button:Cancel'),
  965. 'title' => Dict::S('UI:RelationOption:Title'),
  966. 'include_list' => Dict::S('UI:RelationOption:IncludeList'),
  967. 'comments' => Dict::S('UI:RelationOption:Comments'),
  968. 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'),
  969. 'refresh' => Dict::S('UI:Button:Refresh'),
  970. ),
  971. 'page_format' => array(
  972. 'label' => Dict::S('UI:Relation:PDFExportPageFormat'),
  973. 'values' => array(
  974. 'A3' => Dict::S('UI:PageFormat_A3'),
  975. 'A4' => Dict::S('UI:PageFormat_A4'),
  976. 'Letter' => Dict::S('UI:PageFormat_Letter'),
  977. ),
  978. ),
  979. 'page_orientation' => array(
  980. 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'),
  981. 'values' => array(
  982. 'P' => Dict::S('UI:PageOrientation_Portrait'),
  983. 'L' => Dict::S('UI:PageOrientation_Landscape'),
  984. ),
  985. ),
  986. );
  987. $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");");
  988. }
  989. catch(Exception $e)
  990. {
  991. $oP->add('<div>'.$e->getMessage().'</div>');
  992. }
  993. $oP->add_script(
  994. <<<EOF
  995. function DoReload()
  996. {
  997. $('#ReloadMovieBtn').button('disable');
  998. try
  999. {
  1000. var aExcluded = [];
  1001. $('input[name^=excluded]').each( function() {
  1002. if (!$(this).prop('checked'))
  1003. {
  1004. aExcluded.push($(this).val());
  1005. }
  1006. } );
  1007. $('#graph').simple_graph('option', {excluded_classes: aExcluded});
  1008. $('#graph').simple_graph('reload');
  1009. }
  1010. catch(err)
  1011. {
  1012. alert(err);
  1013. }
  1014. }
  1015. EOF
  1016. );
  1017. }
  1018. }