home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / Image / GraphViz.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  15.7 KB  |  573 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3.  
  4. /**
  5.  * Image_GraphViz
  6.  *
  7.  * Copyright (c) 2001-2006, Dr. Volker G÷bbels <vmg@arachnion.de> and
  8.  * Sebastian Bergmann <sb@sebastian-bergmann.de>. All rights reserved.
  9.  *
  10.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  11.  * that is available through the world-wide-web at the following URI:
  12.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  13.  * the PHP License and are unable to obtain it through the web, please
  14.  * send a note to license@php.net so we can mail you a copy immediately.
  15.  *
  16.  * @category   Image
  17.  * @package    GraphViz
  18.  * @author     Dr. Volker G÷bbels <vmg@arachnion.de>
  19.  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
  20.  * @author     Karsten Dambekalns <k.dambekalns@fishfarm.de>
  21.  * @author     Michael Lively Jr. <mlively@ft11.net>
  22.  * @copyright  2001-2006 Sebastian Bergmann <sb@sebastian-bergmann.de>
  23.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  24.  * @version    CVS: $Id: GraphViz.php,v 1.25 2006/05/16 15:49:19 sebastian Exp $
  25.  * @link       http://pear.php.net/package/Image_GraphViz
  26.  * @since      File available since Release 0.1
  27.  */
  28.  
  29. require_once 'System.php';
  30.  
  31. /**
  32.  * Interface to AT&T's GraphViz tools.
  33.  *
  34.  * The GraphViz class allows for the creation of and the work with directed
  35.  * and undirected graphs and their visualization with AT&T's GraphViz tools.
  36.  *
  37.  * <code>
  38.  * <?php
  39.  * require_once 'Image/GraphViz.php';
  40.  *
  41.  * $graph = new Image_GraphViz();
  42.  *
  43.  * $graph->addNode(
  44.  *   'Node1',
  45.  *   array(
  46.  *     'URL'   => 'http://link1',
  47.  *     'label' => 'This is a label',
  48.  *     'shape' => 'box'
  49.  *   )
  50.  * );
  51.  *
  52.  * $graph->addNode(
  53.  *   'Node2',
  54.  *   array(
  55.  *     'URL'      => 'http://link2',
  56.  *     'fontsize' => '14'
  57.  *   )
  58.  * );
  59.  *
  60.  * $graph->addNode(
  61.  *   'Node3',
  62.  *   array(
  63.  *     'URL'      => 'http://link3',
  64.  *     'fontsize' => '20'
  65.  *   )
  66.  * );
  67.  *
  68.  * $graph->addEdge(
  69.  *   array(
  70.  *     'Node1' => 'Node2'
  71.  *   ),
  72.  *   array(
  73.  *     'label' => 'Edge Label'
  74.  *   )
  75.  * );
  76.  *
  77.  * $graph->addEdge(
  78.  *   array(
  79.  *     'Node1' => 'Node2'
  80.  *   ),
  81.  *   array(
  82.  *     'color' => 'red'
  83.  *   )
  84.  * );
  85.  *
  86.  * $graph->image();
  87.  * ?>
  88.  * </code>
  89.  *
  90.  * @category  Image
  91.  * @package   GraphViz
  92.  * @author    Sebastian Bergmann <sb@sebastian-bergmann.de>
  93.  * @author    Dr. Volker G÷bbels <vmg@arachnion.de>
  94.  * @author    Karsten Dambekalns <k.dambekalns@fishfarm.de>
  95.  * @author    Michael Lively Jr. <mlively@ft11.net>
  96.  * @copyright Copyright © 2001-2006 Dr. Volker G÷bbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
  97.  * @license   http://www.php.net/license/3_0.txt The PHP License, Version 3.0
  98.  * @version   Release: 1.2.1
  99.  * @link      http://pear.php.net/package/Image_GraphViz
  100.  * @since     Class available since Release 0.1
  101.  */
  102. class Image_GraphViz
  103. {
  104.     /**
  105.      * Path to GraphViz/dot command
  106.      *
  107.      * @var  string
  108.      */
  109.     var $dotCommand = 'dot';
  110.  
  111.     /**
  112.      * Path to GraphViz/neato command
  113.      *
  114.      * @var  string
  115.      */
  116.     var $neatoCommand = 'neato';
  117.  
  118.     /**
  119.      * Representation of the graph
  120.      *
  121.      * @var  array
  122.      */
  123.     var $graph;
  124.  
  125.     /**
  126.      * Constructor.
  127.      *
  128.      * Setting the name of the Graph is useful for including multiple image maps on
  129.      * one page. If not set, the graph will be named 'G'.
  130.      *
  131.      * @param  boolean $directed Directed (TRUE) or undirected (FALSE) graph.
  132.      * @param  array   $attributes Attributes of the graph
  133.      * @param  string  $name Name of the Graph
  134.      * @access public
  135.      */
  136.     function Image_GraphViz($directed = TRUE, $attributes = array(), $name = NULL)
  137.     {
  138.         $this->setDirected($directed);
  139.         $this->setAttributes($attributes);
  140.         $this->graph['name'] = $name;
  141.     }
  142.  
  143.     /**
  144.      * Output image of the graph in a given format.
  145.      *
  146.      * @param  string  Format of the output image.
  147.      *                 This may be one of the formats supported by GraphViz.
  148.      * @access public
  149.      */
  150.     function image($format = 'svg')
  151.     {
  152.         if ($data = $this->fetch($format)) {
  153.             $sendContentLengthHeader = TRUE;
  154.  
  155.             switch ($format) {
  156.                 case 'gif':
  157.                 case 'png':
  158.                 case 'wbmp': {
  159.                     header('Content-Type: image/' . $format);
  160.                 }
  161.                 break;
  162.  
  163.                 case 'jpg': {
  164.                     header('Content-Type: image/jpeg');
  165.                 }
  166.                 break;
  167.  
  168.                 case 'pdf': {
  169.                     header('Content-Type: application/pdf');
  170.                 }
  171.                 break;
  172.  
  173.                 case 'svg': {
  174.                     header('Content-Type: image/svg+xml');
  175.                 }
  176.                 break;
  177.  
  178.                 default: {
  179.                     $sendContentLengthHeader = FALSE;
  180.                 }
  181.             }
  182.  
  183.             if ($sendContentLengthHeader) {
  184.                 header('Content-Length: ' . strlen($data));
  185.             }
  186.  
  187.             echo $data;
  188.         }
  189.     }
  190.  
  191.     /**
  192.      * Return image (data) of the graph in a given format.
  193.      *
  194.      * @param  string  Format of the output image.
  195.      *                 This may be one of the formats supported by GraphViz.
  196.      * @return string  The image (data) created by GraphViz.
  197.      * @access public
  198.      * @since  Method available since Release 1.1.0
  199.      */
  200.     function fetch($format = 'svg')
  201.     {
  202.         if ($file = $this->saveParsedGraph()) {
  203.             $outputfile = $file . '.' . $format;
  204.             $command  = $this->graph['directed'] ? $this->dotCommand : $this->neatoCommand;
  205.             $command .= ' -T' . escapeshellarg($format) . ' -o'  . escapeshellarg($outputfile) . ' ' . escapeshellarg($file);
  206.     
  207.             @`$command`;
  208.             @unlink($file);
  209.     
  210.             $fp = fopen($outputfile, 'rb');
  211.     
  212.             if ($fp) {
  213.                 $data = fread($fp, filesize($outputfile));
  214.                 fclose($fp);
  215.                 @unlink($outputfile);
  216.             }
  217.     
  218.             return $data;
  219.         }
  220.     
  221.         return FALSE;
  222.     }
  223.  
  224.     /**
  225.      * Render a given dot file into another format.
  226.      *
  227.      * @param string The absolute path of the dot file to use.
  228.      * @param string The absolute path of the file to save to.
  229.      * @param string Format of the output image.
  230.      *               This may be one of the formats supported by GraphViz.
  231.      * @return bool  True if the file was saved, false otherwise.
  232.      * @access public
  233.      */
  234.     function renderDotFile($dotfile, $outputfile, $format = 'svg')
  235.     {
  236.         if (file_exists($dotfile)) {
  237.             $oldmtime = file_exists($outputfile) ? filemtime($outputfile) : 0;
  238.  
  239.             $command  = $this->graph['directed'] ? $this->dotCommand : $this->neatoCommand;
  240.             $command .= ' -T' . escapeshellarg($format) . ' -o'  . escapeshellarg($outputfile) . ' ' . escapeshellarg($dotfile);
  241.     
  242.             @`$command`;
  243.     
  244.             if (file_exists($outputfile) && filemtime($outputfile) > $oldmtime) {
  245.                 return TRUE;
  246.             }
  247.         }
  248.  
  249.         return FALSE;
  250.     }
  251.  
  252.     /**
  253.      * Add a cluster to the graph.
  254.      *
  255.      * @param  string  ID.
  256.      * @param  array   Title.
  257.      * @param  array   Attributes of the cluster.
  258.      * @access public
  259.      */
  260.     function addCluster($id, $title, $attributes = array())
  261.     {
  262.         $this->graph['clusters'][$id]['title'] = $title;
  263.         $this->graph['clusters'][$id]['attributes'] = $attributes;
  264.     }
  265.  
  266.     /**
  267.      * Add a note to the graph.
  268.      *
  269.      * @param  string  Name of the node.
  270.      * @param  array   Attributes of the node.
  271.      * @param  string  Group of the node.
  272.      * @access public
  273.      */
  274.     function addNode($name, $attributes = array(), $group = 'default')
  275.     {
  276.         $this->graph['nodes'][$group][$name] = $attributes;
  277.     }
  278.  
  279.     /**
  280.      * Remove a node from the graph.
  281.      *
  282.      * @param  Name of the node to be removed.
  283.      * @access public
  284.      */
  285.     function removeNode($name, $group = 'default')
  286.     {
  287.         if (isset($this->graph['nodes'][$group][$name])) {
  288.             unset($this->graph['nodes'][$group][$name]);
  289.         }
  290.     }
  291.  
  292.     /**
  293.      * Add an edge to the graph.
  294.      *
  295.      * Caveat! This cannot handle multiple identical edges. If you use non-numeric
  296.      * IDs for the nodes, this will not do (too much) harm. For numeric IDs the
  297.      * array_merge() that is used will change the keys when merging arrays, leading
  298.      * to new nodes appearing in the graph.
  299.      *
  300.      * @param  array Start and End node of the edge.
  301.      * @param  array Attributes of the edge.
  302.      * @access public
  303.      */
  304.     function addEdge($edge, $attributes = array())
  305.     {
  306.         if (is_array($edge)) {
  307.             $from = key($edge);
  308.             $to   = $edge[$from];
  309.             $id   = $from . '_' . $to;
  310.  
  311.             if (!isset($this->graph['edges'][$id])) {
  312.                 $this->graph['edges'][$id] = $edge;
  313.             } else {
  314.                 $this->graph['edges'][$id] = array_merge(
  315.                   $this->graph['edges'][$id],
  316.                   $edge
  317.                 );
  318.             }
  319.  
  320.             if (is_array($attributes)) {
  321.                 if (!isset($this->graph['edgeAttributes'][$id])) {
  322.                     $this->graph['edgeAttributes'][$id] = $attributes;
  323.                 } else {
  324.                     $this->graph['edgeAttributes'][$id] = array_merge(
  325.                       $this->graph['edgeAttributes'][$id],
  326.                       $attributes
  327.                     );
  328.                 }
  329.             }
  330.         }
  331.     }
  332.  
  333.     /**
  334.      * Remove an edge from the graph.
  335.      *
  336.      * @param  array Start and End node of the edge to be removed.
  337.      * @access public
  338.      */
  339.     function removeEdge($edge)
  340.     {
  341.         if (is_array($edge)) {
  342.               $from = key($edge);
  343.               $to   = $edge[$from];
  344.               $id   = $from . '_' . $to;
  345.  
  346.             if (isset($this->graph['edges'][$id])) {
  347.                 unset($this->graph['edges'][$id]);
  348.             }
  349.  
  350.             if (isset($this->graph['edgeAttributes'][$id])) {
  351.                 unset($this->graph['edgeAttributes'][$id]);
  352.             }
  353.         }
  354.     }
  355.  
  356.     /**
  357.      * Add attributes to the graph.
  358.      *
  359.      * @param  array Attributes to be added to the graph.
  360.      * @access public
  361.      */
  362.     function addAttributes($attributes)
  363.     {
  364.         if (is_array($attributes)) {
  365.             $this->graph['attributes'] = array_merge(
  366.               $this->graph['attributes'],
  367.               $attributes
  368.             );
  369.         }
  370.     }
  371.  
  372.     /**
  373.      * Set attributes of the graph.
  374.      *
  375.      * @param  array Attributes to be set for the graph.
  376.      * @access public
  377.      */
  378.     function setAttributes($attributes)
  379.     {
  380.         if (is_array($attributes)) {
  381.             $this->graph['attributes'] = $attributes;
  382.         }
  383.     }
  384.  
  385.     /**
  386.      * Set directed/undirected flag for the graph.
  387.      *
  388.      * @param  boolean Directed (TRUE) or undirected (FALSE) graph.
  389.      * @access public
  390.      */
  391.     function setDirected($directed)
  392.     {
  393.         if (is_bool($directed)) {
  394.             $this->graph['directed'] = $directed;
  395.         }
  396.     }
  397.  
  398.     /**
  399.      * Load graph from file.
  400.      *
  401.      * @param  string  File to load graph from.
  402.      * @access public
  403.      */
  404.     function load($file)
  405.     {
  406.         if ($serializedGraph = implode('', @file($file))) {
  407.             $this->graph = unserialize($serializedGraph);
  408.         }
  409.     }
  410.  
  411.     /**
  412.      * Save graph to file.
  413.      *
  414.      * @param  string  File to save the graph to.
  415.      * @return mixed   File the graph was saved to, FALSE on failure.
  416.      * @access public
  417.      */
  418.     function save($file = '')
  419.     {
  420.         $serializedGraph = serialize($this->graph);
  421.  
  422.         if (empty($file)) {
  423.             $file = System::mktemp('graph_');
  424.         }
  425.  
  426.         if ($fp = @fopen($file, 'w')) {
  427.             @fputs($fp, $serializedGraph);
  428.             @fclose($fp);
  429.  
  430.             return $file;
  431.         }
  432.  
  433.         return FALSE;
  434.     }
  435.  
  436.     /**
  437.      * Parse the graph into GraphViz markup.
  438.      *
  439.      * @return string  GraphViz markup
  440.      * @access public
  441.      */
  442.     function parse()
  443.     {
  444.         if (isset($this->graph['name']) && is_string($this->graph['name'])) {
  445.             $parsedGraph = "digraph " . $this->graph['name'] . " {\n";
  446.         } else {
  447.             $parsedGraph = "digraph G {\n";
  448.         }
  449.  
  450.         if (isset($this->graph['attributes'])) {
  451.             foreach ($this->graph['attributes'] as $key => $value) {
  452.                 $attributeList[] = $key . '="' . $value . '"';
  453.             }
  454.  
  455.             if (!empty($attributeList)) {
  456.                 $parsedGraph .= 'graph [ '.implode(',', $attributeList) . " ];\n";
  457.             }
  458.         }
  459.  
  460.         if (isset($this->graph['nodes'])) {
  461.             foreach($this->graph['nodes'] as $group => $nodes) {
  462.                 if ($group != 'default') {
  463.                     $parsedGraph .= sprintf(
  464.                       "subgraph \"cluster_%s\" {\nlabel=\"%s\";\n",
  465.  
  466.                       $group,
  467.                       isset($this->graph['clusters'][$group]) ? $this->graph['clusters'][$group]['title'] : ''
  468.                     );
  469.  
  470.                     if (isset($this->graph['clusters'][$group]['attributes'])) {
  471.                         unset($attributeList);
  472.  
  473.                         foreach ($this->graph['clusters'][$group]['attributes'] as $key => $value) {
  474.                             $attributeList[] = $key . '="' . $value . '"';
  475.                         }
  476.  
  477.                         if (!empty($attributeList)) {
  478.                             $parsedGraph .= implode(',', $attributeList) . ";\n";
  479.                         }
  480.                     }
  481.                 }
  482.  
  483.                 foreach($nodes as $node => $attributes) {
  484.                     unset($attributeList);
  485.  
  486.                     foreach($attributes as $key => $value) {
  487.                         $attributeList[] = $key . '="' . $value . '"';
  488.                     }
  489.  
  490.                     if (!empty($attributeList)) {
  491.                         $parsedGraph .= sprintf(
  492.                           "\"%s\" [ %s ];\n",
  493.                           addslashes(stripslashes($node)),
  494.                           implode(',', $attributeList)
  495.                         );
  496.                     }
  497.                 }
  498.  
  499.                 if ($group != 'default') {
  500.                   $parsedGraph .= "}\n";
  501.                 }
  502.             }
  503.         }
  504.  
  505.         if (isset($this->graph['edges'])) {
  506.             foreach($this->graph['edges'] as $label => $node) {
  507.                 unset($attributeList);
  508.  
  509.                 $from = key($node);
  510.                 $to   = $node[$from];
  511.  
  512.                 foreach($this->graph['edgeAttributes'][$label] as $key => $value) {
  513.                     $attributeList[] = $key . '="' . $value . '"';
  514.                 }
  515.  
  516.                 $parsedGraph .= sprintf(
  517.                   '"%s" -> "%s"',
  518.                   addslashes(stripslashes($from)),
  519.                   addslashes(stripslashes($to))
  520.                 );
  521.                 
  522.                 if (!empty($attributeList)) {
  523.                     $parsedGraph .= sprintf(
  524.                       ' [ %s ]',
  525.                       implode(',', $attributeList)
  526.                     );
  527.                 }
  528.  
  529.                 $parsedGraph .= ";\n";
  530.             }
  531.         }
  532.  
  533.         return $parsedGraph . "}\n";
  534.     }
  535.  
  536.     /**
  537.      * Save GraphViz markup to file.
  538.      *
  539.      * @param  string  File to write the GraphViz markup to.
  540.      * @return mixed   File to which the GraphViz markup was
  541.      *                 written, FALSE on failure.
  542.      * @access public
  543.      */
  544.     function saveParsedGraph($file = '')
  545.     {
  546.         $parsedGraph = $this->parse();
  547.  
  548.         if (!empty($parsedGraph)) {
  549.             if (empty($file)) {
  550.                 $file = System::mktemp('graph_');
  551.             }
  552.  
  553.             if ($fp = @fopen($file, 'w')) {
  554.                 @fputs($fp, $parsedGraph);
  555.                 @fclose($fp);
  556.  
  557.                 return $file;
  558.             }
  559.         }
  560.  
  561.         return FALSE;
  562.     }
  563. }
  564.  
  565. /*
  566.  * Local variables:
  567.  * tab-width: 4
  568.  * c-basic-offset: 4
  569.  * c-hanging-comment-ender-p: nil
  570.  * End:
  571.  */
  572. ?>
  573.