|
@@ -236,134 +236,213 @@ class DisplayableNode extends GraphNode
|
|
return is_object($this->GetProperty('object', null)) ? get_class($this->GetProperty('object', null)) : null;
|
|
return is_object($this->GetProperty('object', null)) ? get_class($this->GetProperty('object', null)) : null;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ protected function AddToStats($oNode, &$aNodesPerClass)
|
|
|
|
+ {
|
|
|
|
+ $sClass = $oNode->GetObjectClass();
|
|
|
|
+ if (!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'] += $oNode->GetObjectCount();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
- * Group together (as a special kind of nodes) all the similar neighbours of the current node
|
|
|
|
- * @param DisplayableGraph $oGraph
|
|
|
|
- * @param int $iThresholdCount
|
|
|
|
- * @param boolean $bDirectionUp
|
|
|
|
- * @param boolean $bDirectionDown
|
|
|
|
|
|
+ * Retrieves the list of neighbour nodes, in the given direction: 'up' or 'down'
|
|
|
|
+ * @param bool $bDirectionDown
|
|
|
|
+ * @return multitype:NULL
|
|
*/
|
|
*/
|
|
- public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
|
|
|
|
|
|
+ protected function GetNextNodes($bDirectionDown = true)
|
|
{
|
|
{
|
|
- if ($this->GetProperty('grouped') === true) return;
|
|
|
|
- $this->SetProperty('grouped', true);
|
|
|
|
-
|
|
|
|
|
|
+ $aNextNodes = array();
|
|
if ($bDirectionDown)
|
|
if ($bDirectionDown)
|
|
{
|
|
{
|
|
- $aNodesPerClass = array();
|
|
|
|
foreach($this->GetOutgoingEdges() as $oEdge)
|
|
foreach($this->GetOutgoingEdges() as $oEdge)
|
|
{
|
|
{
|
|
- $oNode = $oEdge->GetSinkNode();
|
|
|
|
- $sClass = $oNode->GetObjectClass();
|
|
|
|
- if ($sClass !== null)
|
|
|
|
|
|
+ $aNextNodes[] = $oEdge->GetSinkNode();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ foreach($this->GetIncomingEdges() as $oEdge)
|
|
|
|
+ {
|
|
|
|
+ $aNextNodes[] = $oEdge->GetSourceNode();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return $aNextNodes;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Replaces the next neighbour node (in the given direction: 'up' or 'down') by the supplied group node
|
|
|
|
+ * preserving the connectivity of the graph
|
|
|
|
+ * @param DisplayableGraph $oGraph
|
|
|
|
+ * @param DisplayableNode $oNextNode
|
|
|
|
+ * @param DisplayableGroupNode $oNewNode
|
|
|
|
+ * @param bool $bDirectionDown
|
|
|
|
+ */
|
|
|
|
+ protected function ReplaceNextNodeBy(DisplayableGraph $oGraph, DisplayableNode $oNextNode, DisplayableGroupNode $oNewNode, $bDirectionDown = true)
|
|
|
|
+ {
|
|
|
|
+ $sClass = $oNewNode->GetProperty('class');
|
|
|
|
+ if ($bDirectionDown)
|
|
|
|
+ {
|
|
|
|
+ foreach($oNextNode->GetIncomingEdges() as $oEdge)
|
|
|
|
+ {
|
|
|
|
+ if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
|
|
{
|
|
{
|
|
- if (!array_key_exists($sClass, $aNodesPerClass))
|
|
|
|
|
|
+ try
|
|
{
|
|
{
|
|
- $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'),
|
|
|
|
- )
|
|
|
|
- );
|
|
|
|
|
|
+ $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
|
}
|
|
}
|
|
- $sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
|
|
|
|
- if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
|
|
|
|
|
|
+ catch(Exception $e)
|
|
{
|
|
{
|
|
- $aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
|
|
|
|
- $aNodesPerClass[$sClass][$sKey]['count'] += $oNode->GetObjectCount();
|
|
|
|
|
|
+ // ignore this edge
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+ }
|
|
|
|
+ foreach($oNextNode->GetOutgoingEdges() as $oEdge)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
|
|
|
+ }
|
|
|
|
+ catch(Exception $e)
|
|
{
|
|
{
|
|
- $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
|
|
|
|
+ // ignore this edge
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- foreach($aNodesPerClass as $sClass => $aDefs)
|
|
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ foreach($oNextNode->GetOutgoingEdges() as $oEdge)
|
|
{
|
|
{
|
|
- foreach($aDefs as $sStatus => $aGroupProps)
|
|
|
|
|
|
+ if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
|
|
{
|
|
{
|
|
- if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
|
|
|
|
|
+ try
|
|
{
|
|
{
|
|
- $sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': '');
|
|
|
|
- $oNewNode = $oGraph->GetNode($sNewId);
|
|
|
|
- if ($oNewNode == null)
|
|
|
|
- {
|
|
|
|
- $oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
|
|
|
- $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']);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- try
|
|
|
|
|
|
+ $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
|
|
|
+ }
|
|
|
|
+ catch(Exception $e)
|
|
|
|
+ {
|
|
|
|
+ // ignore this edge
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ foreach($oNextNode->GetIncomingEdges() as $oEdge)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
|
|
|
+ }
|
|
|
|
+ catch(Exception $e)
|
|
|
|
+ {
|
|
|
|
+ // ignore this edge
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($oGraph->GetNode($oNextNode->GetId()))
|
|
|
|
+ {
|
|
|
|
+ $oGraph->_RemoveNode($oNextNode);
|
|
|
|
+ if ($oNextNode instanceof DisplayableGroupNode)
|
|
|
|
+ {
|
|
|
|
+ // Copy all the objects of the previous group into the new group
|
|
|
|
+ foreach($oNextNode->GetObjects() as $oObj)
|
|
|
|
+ {
|
|
|
|
+ $oNewNode->AddObject($oObj);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ $oNewNode->AddObject($oNextNode->GetProperty('object'));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Group together (as a special kind of nodes) all the similar neighbours of the current node
|
|
|
|
+ * @param DisplayableGraph $oGraph
|
|
|
|
+ * @param int $iThresholdCount
|
|
|
|
+ * @param boolean $bDirectionUp
|
|
|
|
+ * @param boolean $bDirectionDown
|
|
|
|
+ */
|
|
|
|
+ public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
|
|
|
|
+ {
|
|
|
|
+ if ($this->GetProperty('grouped') === true) return;
|
|
|
|
+ $this->SetProperty('grouped', true);
|
|
|
|
+
|
|
|
|
+ $aNodesPerClass = array();
|
|
|
|
+ foreach($this->GetNextNodes($bDirectionDown) as $oNode)
|
|
|
|
+ {
|
|
|
|
+ $sClass = $oNode->GetObjectClass();
|
|
|
|
+ if ($sClass !== null)
|
|
|
|
+ {
|
|
|
|
+ $this->AddToStats($oNode, $aNodesPerClass);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ foreach($aNodesPerClass as $sClass => $aDefs)
|
|
|
|
+ {
|
|
|
|
+ foreach($aDefs as $sStatus => $aGroupProps)
|
|
|
|
+ {
|
|
|
|
+ if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
|
|
|
+ {
|
|
|
|
+ $sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': '');
|
|
|
|
+ $oNewNode = $oGraph->GetNode($sNewId);
|
|
|
|
+ if ($oNewNode == null)
|
|
|
|
+ {
|
|
|
|
+ $oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
|
|
|
+ $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']);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ if ($bDirectionDown)
|
|
{
|
|
{
|
|
$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
|
|
$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
|
|
}
|
|
}
|
|
- catch(Exception $e)
|
|
|
|
|
|
+ else
|
|
{
|
|
{
|
|
- // Ignore this redundant egde
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- foreach($aGroupProps['nodes'] as $oNode)
|
|
|
|
- {
|
|
|
|
- foreach($oNode->GetIncomingEdges() as $oEdge)
|
|
|
|
- {
|
|
|
|
- if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
|
|
|
|
- {
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
|
|
|
- }
|
|
|
|
- catch(Exception $e)
|
|
|
|
- {
|
|
|
|
- // ignore this edge
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- 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);
|
|
|
|
- if ($oNode instanceof DisplayableGroupNode)
|
|
|
|
- {
|
|
|
|
- // Copy all the objects of the previous group into the new group
|
|
|
|
- foreach($oNode->GetObjects() as $oObj)
|
|
|
|
- {
|
|
|
|
- $oNewNode->AddObject($oObj);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- $oNewNode->AddObject($oNode->GetProperty('object'));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ $oOutgoingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $oNewNode, $this);
|
|
}
|
|
}
|
|
- $oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+ catch(Exception $e)
|
|
{
|
|
{
|
|
- foreach($aGroupProps['nodes'] as $oNode)
|
|
|
|
- {
|
|
|
|
- $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
|
|
- }
|
|
|
|
|
|
+ // Ignore this redundant egde
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ foreach($aGroupProps['nodes'] as $oNextNode)
|
|
|
|
+ {
|
|
|
|
+ $this->ReplaceNextNodeBy($oGraph, $oNextNode, $oNewNode, $bDirectionDown);
|
|
|
|
+ }
|
|
|
|
+ $oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ foreach($aGroupProps['nodes'] as $oNode)
|
|
|
|
+ {
|
|
|
|
+ $oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -536,8 +615,11 @@ class DisplayableRedundancyNode extends DisplayableNode
|
|
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
|
|
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- $oGraph->_RemoveNode($oNode);
|
|
|
|
- $oNewNode->AddObject($oNode->GetProperty('object'));
|
|
|
|
|
|
+ if ($oGraph->HasNode($oNode->GetId()))
|
|
|
|
+ {
|
|
|
|
+ $oGraph->_RemoveNode($oNode);
|
|
|
|
+ $oNewNode->AddObject($oNode->GetProperty('object'));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
//$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
//$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
|
}
|
|
}
|
|
@@ -627,14 +709,17 @@ class DisplayableGroupNode extends DisplayableNode
|
|
$this->aObjects = array();
|
|
$this->aObjects = array();
|
|
}
|
|
}
|
|
|
|
|
|
- public function AddObject(DBObject $oObj)
|
|
|
|
|
|
+ public function AddObject(DBObject $oObj = null)
|
|
{
|
|
{
|
|
- $sPrevClass = $this->GetObjectClass();
|
|
|
|
- if (($sPrevClass !== null) && (get_class($oObj) !== $sPrevClass))
|
|
|
|
|
|
+ if (is_object($oObj))
|
|
{
|
|
{
|
|
- throw new Exception("Error: adding an object of class '".get_class($oObj)."' to a group of '$sPrevClass' objects.");
|
|
|
|
|
|
+ $sPrevClass = $this->GetObjectClass();
|
|
|
|
+ if (($sPrevClass !== null) && (get_class($oObj) !== $sPrevClass))
|
|
|
|
+ {
|
|
|
|
+ throw new Exception("Error: adding an object of class '".get_class($oObj)."' to a group of '$sPrevClass' objects.");
|
|
|
|
+ }
|
|
|
|
+ $this->aObjects[$oObj->GetKey()] = $oObj;
|
|
}
|
|
}
|
|
- $this->aObjects[$oObj->GetKey()] = $oObj;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public function GetObjects()
|
|
public function GetObjects()
|
|
@@ -863,9 +948,13 @@ class DisplayableGraph extends SimpleGraph
|
|
foreach($oNodesIter as $oNode)
|
|
foreach($oNodesIter as $oNode)
|
|
{
|
|
{
|
|
set_time_limit($iLoopTimeLimit);
|
|
set_time_limit($iLoopTimeLimit);
|
|
- if ($oNode->GetProperty('source'))
|
|
|
|
|
|
+ if ($bDirectionDown && $oNode->GetProperty('source'))
|
|
|
|
+ {
|
|
|
|
+ $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, $bDirectionDown);
|
|
|
|
+ }
|
|
|
|
+ else if (!$bDirectionDown && $oNode->GetProperty('sink'))
|
|
{
|
|
{
|
|
- $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
|
|
|
|
|
|
+ $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, $bDirectionDown);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Groups numbering
|
|
// Groups numbering
|