Guía 9 – Dibujar Organigrama

En este caso, la solicitud de un compañero de PHPRunner fue la de poder representar el Organigrama de una compañía.

Su problema inicial era la utilización de una librería de JavaScript que tenía esta función, pero desde mi punto de vista , lo complejo es “crear” la estructura jerárquica que hay que dar a estas librerías para que representen el citado Organigrama.

La librería que hemos seleccionado es OrgChart que como podréis observar, dispone de muchas funcionalidades y facilita muchos ejemplos que hace mucho más sencillo su utilización.

Objetivo

Me he puesto este conjunto de requisitos:

  • La definición de la jerarquía de la organización debe ser muy sencilla de definir. Solamente es necesario definir el jefe inmediato de cada una de las personas. Para el ejemplo, solamente puede haber una única persona que no tiene jefe inmediato.
  • Se quiere que en la representación de cada una de las personas se disponga de información adicional, en concreto, que pueda representarse la foto de cada uno. Esto se puede extender a casi cualquier cosa.

DEMO:  https://fhumanes.com/orgchart/

Solución Técnica

He utilizado PHPRunner 10.4, pero podría haber utilizado cualquier versión 10.X, pues cualquiera de ellas sería igual de válida.

La integración de la librería JavaScript OrgChart la he hecho utilizando una página “LIST”, de una vista de la misma tabla de “USER” y he eliminado la presentación de todos los campos y de la información de paginación.

A este diseño añadí un “Snippet” que es donde he puesto el código para la presentación del gráfico. Esto lo he escrito en 2 ficheros.

En el fichero “hierarchy.php”, para mí el más importante, es donde recuperamos la información de todos los usuarios de la base de datos y creamos un array con todos ellos.

Este Array, se lo pasamos a la función “convertToHierarchy” y convierte la misma en otro array que representa la jerarquía de la estructura. Esta función es imprescindible para el tratamiento de  datos jerárquicos.  Más información en:  https://gist.github.com/ubermaniac/8834601

Una vez que tenemos la jerarquía en el array hay que crear la estructura de datos que la librería JavaScript requiere. Esto es bastante sencillo si utilizamos un código recursivo para generar la información.

<?php
//  Create ARRAY with the data Hierarchy
function convertToHierarchy($results, $idField='user_id', $parentIdField='Boss', $childrenField='') {
      $hierarchy = array(); // -- Stores the final data
      $itemReferences = array(); // -- temporary array, storing references to all items in a single-dimention
      foreach ( $results as $item ) {
            $id       = $item[$idField];
            $parentId = $item[$parentIdField];
            if (isset($itemReferences[$parentId])) { // parent exists
                  $itemReferences[$parentId][$childrenField][$id] = $item; // assign item to parent
                  $itemReferences[$id] =& $itemReferences[$parentId][$childrenField][$id]; // reference parent's item in single-dimentional array
            } elseif (!$parentId || !isset($hierarchy[$parentId])) { // -- parent Id empty or does not exist. Add it to the root
                  $hierarchy[$id] = $item;
                  $itemReferences[$id] =& $hierarchy[$id];
            }
      }
      unset($results, $item, $id, $parentId);
      // -- Run through the root one more time. If any child got added before it's parent, fix it.
      foreach ( $hierarchy as $id => &$item ) {
            $parentId = $item[$parentIdField];
            if ( isset($itemReferences[$parentId] ) ) { // -- parent DOES exist
                  $itemReferences[$parentId][$childrenField][$id] = $item; // -- assign it to the parent's list of children
                  unset($hierarchy[$id]); // -- remove it from the root of the hierarchy
            }
      }
      unset($itemReferences, $id, $item, $parentId);
      return $hierarchy;
} // 

function identation($level) {
    $identation ='';
    for ($i = 1;$i < $level; $i++) {
        $identation .= "\t";
    }
    return $identation;
}
// Print data of record 
function printRow($level, $row) {
    global $datasource;
    $datasource .= identation($level)."{ 'id': '".$row['id_user']."', 'name': '".addslashes($row['name'])."', 'title': '".addslashes($row['category'])."', 'picture': '".addslashes($row['picture'])."'";
};
function printHierarchy ($level, $Hierarchy) {
    global $datasource;

    do {
        $key = key($Hierarchy);
        $value = current($Hierarchy);
        printRow($level+1, $value);
         if ( is_array($value[children])){
             $datasource .= ",\n".identation($level+1)."  'children': [ \n";
             printHierarchy ($level+1, $value[children]);  // Recursive
             $datasource .= "\n".identation($level+1)."  ]";
         }            
        $datasource .= "},\n";     
    } while (next($Hierarchy));
    $datasource = substr($datasource, 0, -2); // Delete last ","
}

global $conn;
// Create auxiliary Sorting tables
$results = array();
$sql = "SELECT id_orgchart_user id_user, ifnull(boss_id,'') id_parent , name, category,  picture FROM orgchart_user";
$resql = DB::Query($sql); 
    /* get associative array */
while ($row = $resql->fetchAssoc()) {
  $fileArray = my_json_decode($row["picture"]);
  $picture = $fileArray[0]["name"];
  if ($picture == NULL) $picture = 'files/no_existe.png';
  $results[] = ['id_user' =>$row['id_user'],'id_parent' =>$row['id_parent'],'name' =>$row['name'], 'category' =>$row['category'],'picture' =>$picture];
  // $results[] = $row;
 }
$Hierarchy = convertToHierarchy($results,'id_user','id_parent','children'); // Create hierarchy from data

unset($results); // Delete variable
$level = 0;

$datasource = '';
global $datasource;

if ( count($Hierarchy) <> 0 ) {
    printHierarchy ($level, $Hierarchy); 
}
?>

Una vez que tenemos la información de la estructura, lo único es utilizar la citada librería de JavaScript y esto se hace en el fichero ”OrgChart.php”.

<?php
include __DIR__.'/hierarchy.php';

$html = <<<EOT
<link rel="stylesheet" href="lib_orgchart/css/jquery.orgchart.css">
<style type="text/css">
#chart-container {
  position: relative;
  height: 520px;
  border: 1px solid #aaa;
  margin: 0.5rem;
  overflow: auto;
  text-align: center;
}
 .orgchart { background: #fff; }
 .orgchart .second-menu-icon {
  transition: opacity .5s;
  opacity: 0;
  right: -5px;
  top: -5px;
  z-index: 2;
  position: absolute;
 }
 .orgchart .second-menu-icon::before { background-color: rgba(68, 157, 68, 0.5); }
 .orgchart .second-menu-icon:hover::before { background-color: #449d44; }
 .orgchart .node:hover .second-menu-icon { opacity: 1; }
 .orgchart .node .second-menu {
  display: none;
  position: absolute;
  top: 0;
  right: -70px;
  border-radius: 35px;
  box-shadow: 0 0 10px 1px #999;
  background-color: #fff;
  z-index: 1;
 }
 .orgchart .node .second-menu .avatar {
  width: 60px;
  height: 60px;
  border-radius: 30px;
  float: left;
  margin: 5px;
 }
</style>

<div id="chart-container"></div>

<script type="text/javascript" src="lib_orgchart/js/jquery.orgchart.js"></script>
<script type="text/javascript">
\$(function() {
 console.log( "ready!" );

 var datascource = $datasource;

 \$('#chart-container').orgchart({
  'data' : datascource,
  'nodeId': 'id',
  'visibleLevel': 2,
  'nodeContent': 'title',
  'toggleSiblingsResp': true,
  'pan': true,
  'zoom': true,
  'zoominLimit': 7,
  'zoomoutLimit': 0.5,
  'chartClass': '',
  'exportButton': false,
  'exportButtonName': 'Export',
  'exportFilename': 'OrgChart',
  'exportFileextension': 'png',
  'parentNodeSymbol': 'oci-leader',
  'createNode': function(\$node, data) {
    var secondMenuIcon = \$('<i>', {
     'class': 'oci oci-info-circle second-menu-icon',
     click: function() {
      $(this).siblings('.second-menu').toggle();
     }
    });
    var secondMenu = '<div class="second-menu"><img class="avatar" src="'+ data.picture + '"></div>';
    \$node.append(secondMenuIcon).append(secondMenu);
  }
 });

});
</script>

EOT;
echo $html;

Como he indicado, existe muchas posibilidades de presentación y espero que esta separación de los pasos y el propio ejemplo os faciliten ajustar este ejemplo a vuestras necesidades.

Para cualquier duda o consulta, indicármelo en mi email [email protected]

Os dejo el proyecto, para que lo podáis reproducir en vuestros equipos.

También os dejo la tabla que utilizo y el directorio “files” donde están las imágenes.

Blog personal para facilitar soporte gratuito a usuarios de PHPRunner