continued :)
[phpgitweb.git] / htdocs / lib / graph.class.php
diff --git a/htdocs/lib/graph.class.php b/htdocs/lib/graph.class.php
new file mode 100644 (file)
index 0000000..5cf843e
--- /dev/null
@@ -0,0 +1,389 @@
+<?php
+/* graph.class.php - phpgitweb
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+class graph_data_generator {
+       const DOT_TYPE_NORMAL = 'a';
+       const DOT_TYPE_MERGE = 'b';
+       const DOT_TYPE_INIT = 'c';
+
+       private $data = array();
+       private $graph = array();
+       private $max_branches, $brach_id = 1, $branch_id = 1;
+       
+       public function __construct() {
+               $this->max_branches = 10;
+               $this->data['branches'] = array();
+               $this->data['ubranches'] = array();
+       }
+       
+       public function add_branch($first_id, $name) {
+               $existing = false;
+               foreach($this->data['branches'] as &$branch) {
+                       if($branch['next'] == $first_id) {
+                               $existing = true;
+                               $branch['name'][] = $name;
+                               break;
+                       }
+               }
+               unset($branch);
+               if($existing)
+                       continue;
+               $this->data['branches'][count($this->data['branches'])] = array(
+                       "id" => $this->brach_id++,
+                       "uid" => $this->branch_uid++,
+                       "active" => true,
+                       "sticky" => true,
+                       "name" => array($name),
+                       "next" => $first_id,
+                       "pre_merge" => false
+               );
+       }
+       
+       public function parse($commits) {
+               $brach_id = $this->brach_id;
+               $branch_uid = $this->branch_id;
+               $first_commit = (count($this->data['branches']) == 0 ? true : false);
+               foreach($commits as $commit) {
+                       //get current branch
+                       $commit['merge'] = array();
+                       $commit['dot_type'] = self::DOT_TYPE_NORMAL;
+                       if($first_commit) {
+                               $first_commit = false;
+                               $this->data['branches'][0] = array();
+                               $branch = &$this->data['branches'][0];
+                               $branch['id'] = $brach_id++;
+                               $branch['uid'] = $branch_uid++;
+                               $branch['active'] = true;
+                       } else {
+                               $first = true;
+                               foreach($this->data['branches'] as $id => &$cbranch) {
+                                       if($cbranch['next'] == $commit['id']) {
+                                               if($first && !$cbranch['pre_merge']) {
+                                                       $branch = &$this->data['branches'][$id];
+                                                       $first = false;
+                                               }
+                                       }
+                               }
+                               foreach($this->data['branches'] as $id => &$cbranch) {
+                                       if($cbranch['next'] == $commit['id']) {
+                                               if($first) {
+                                                       $branch = &$this->data['branches'][$id];
+                                                       $first = false;
+                                               } else if($cbranch['id'] == $branch['id']) {
+                                               } else {
+                                                       $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
+                                                       $cbranch['active'] = false;
+                                                       if($cbranch['pre_merge']) {
+                                                               $cbranch['pre_merge_start'] = true;
+                                                               $cbranch['pre_merge_id'] = $branch['id'];
+                                                               $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+                                                       }
+                                               }
+                                       }
+                               }
+                               unset($cbranch);
+                               if($first) {
+                                       $this->data['branches'][count($this->data['branches'])] = array();
+                                       $branch = &$this->data['branches'][count($this->data['branches'])-1];
+                                       $branch['id'] = $brach_id++;
+                                       $branch['uid'] = $branch_uid++;
+                                       $branch['active'] = true;
+                                       $branch['pre_merge'] = false;
+                                       
+                               }
+                       }
+                       
+                       if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
+                               //merge(s)
+                               for($j = 1; $j < count($commit['parent']); $j++) {
+                                       $add = true;
+                                       foreach($this->data['branches'] as $cbranch) {
+                                               if($cbranch['next'] == $commit['parent'][$j]) {
+                                                       $add = false;
+                                                       break;
+                                               }
+                                       }
+                                       if($add) {
+                                               $cadd = true;
+                                               foreach($this->data['branches'] as $bid => &$cbranch) {
+                                                       if(!$cbranch['active']) {
+                                                               $cadd = false;
+                                                               break;
+                                                       }
+                                               }
+                                               if($cadd) {
+                                                       $this->data['branches'][count($this->data['branches'])] = array();
+                                                       $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
+                                                       $cbranch['id'] = $brach_id++;
+                                               }
+                                               $cbranch['uid'] = $branch_uid++;
+                                               $cbranch['active'] = true;
+                                               $cbranch['pre_merge'] = true;
+                                               $cbranch['next'] = $commit['parent'][$j];
+                                       }
+                                       $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
+                                       $commit['dot_type'] = self::DOT_TYPE_MERGE;
+                                       $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+                                       unset($cbranch);
+                               }
+                       } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
+                               $branch['active'] = false;
+                               $commit['dot_type'] = self::DOT_TYPE_INIT;
+                       }
+                       $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
+                       $branch['pre_merge'] = false;
+                       $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
+                       
+                       $commit['dot'] = $branch['id'];
+                       
+                       foreach($this->data['branches'] as $id => $cbranch) {
+                               $commit['branches'][$id] = $cbranch;
+                       }
+                       
+                       $this->graph[$commit['id']] = $this->graph_data($commit);
+                       //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
+               }
+       }
+       
+       private function graph_data($commit) {
+               $data = array();
+               $data['d'] = array();
+               $data['d']['p'] = $commit['dot']; //dot position
+               $data['d']['type'] = 'a';
+               $data['l'] = array(); //lines
+               $data['br'] = array(); //branches for color check
+               foreach($commit['branches'] as $branch) {
+                       if($branch['pre_merge'] || $commit['merge']) {
+                               $data['br'][] = $branch['uid'];
+                       }
+                       if($branch['active']) {
+                               if($commit['dot'] == $branch['id']) continue;
+                               $show = true;
+                               if($commit['merge']) {
+                                       foreach($commit['merge'] as $merge) {
+                                               if($merge['point'] == $branch['id']) {
+                                                       $show = false;
+                                                       break;
+                                               }
+                                       }
+                               }
+                               if(!$show) continue;
+                               if($branch['id'] > $this->max_branches) continue;
+                               $data['l'][] = $branch['id'];
+                       }
+               }
+               $data['m'] = array(); //merges
+               if($commit['merge']) {
+                       foreach($commit['merge'] as $merge) {
+                               $mergepoint = array();
+                               $mergepoint['hl'] = array();
+                               $mergepoint['p'] = $merge['point'];
+                               
+                               if($commit['dot'] <= $this->max_branches)
+                                       $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
+                               else
+                                       $mergepoint['dd'] = 'n';
+                               
+                               $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
+                               if($merge['point'] <= $this->max_branches)
+                                       $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
+                               else
+                                       $mergepoint['md'] = 'n';
+                               $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
+                               $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
+                               for($i = $min; $i < $max; $i++) {
+                                       if($i > $this->max_branches) continue;
+                                       $mergepoint['hl'][] = $i;
+                               }
+                               $data['m'][] = $mergepoint;
+                       }
+               }
+               $data['d']['type'] = $commit['dot_type'];
+               return $data;
+       }
+       
+       public function get_graph($id) {
+               $graph = $this->graph[$id];
+               $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
+               $first_merge = true;
+               foreach($graph['m'] as $merge) {
+                       if(!$first_merge)
+                               $data .= '|';
+                       $first_merge = false;
+                       $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
+                       foreach($merge['hl'] as $hline)
+                               $data.=','.$hline;
+               }
+               $graph['cs'] = array();
+               foreach($graph['br'] as $buid) {
+                       $branch = $this->data['ubranches'][$buid];
+                       if($branch['pre_merge'] && $branch['pre_merge_start'])
+                               $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
+               }
+               if(count($graph['cs'])) {
+                       $data .= '('.implode(',', $graph['cs']).')';
+               }
+               return $data;
+       }
+       
+}
+
+class graph_image_generator {
+       private $max_branches = 10;
+       private $image;
+       private $size = 20;
+       private $tile_size = 20;
+       private $colors, $color_swap = array();
+       
+       public function generate($data) {
+               $data = $this->parse_data($data);
+               if(!$data)
+                       return;
+               
+               $this->colors = array(
+                       NULL,
+                       array(255, 0, 0),
+                       array(array(0, 255, 0), array(0, 192, 0)),
+                       array(0, 0, 255),
+                       array(128, 128, 128),
+                       array(128, 128, 0),
+                       array(0, 128, 128),
+                       array(128, 0, 128)
+               );
+               
+               $count = $data['count'];
+               if($count > $this->max_branches)
+                       $count = $this->max_branches;
+               $this->image = imagecreatetruecolor($count * $this->size, $this->size);
+               $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
+               imagefill($this->image, 0, 0, $transparentIndex);
+               
+               $this->apply_data($data);
+               
+               imagecolortransparent($this->image, $transparentIndex);
+
+               header('Content-Type: image/png');
+               imagepng($this->image);
+               imagedestroy($this->image);
+       }
+       
+       private function parse_data($data) {
+               //$data = array();
+               if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches))
+                       return null;
+               
+               $data = array();
+               $data['dot'] = array();
+               $data['dot']['pos'] = $matches[1];
+               $data['dot']['type'] = $matches[2];
+               $data['count'] = $matches[3];
+               
+               if($matches[4] != '')
+                       $data['l'] = explode(',', $matches[4]);
+               else
+                       $data['l'] = array();
+               
+               $data['m'] = array();
+               if($matches[5] != '') {
+                       foreach(explode('|', $matches[5]) as $m) {
+                               $merge = array();
+                               
+                               if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
+                                       return null;
+                               $merge['hl'] = array();
+                               $merge['pos'] = $sm[1];
+                               $merge['dd'] = $sm[2];
+                               $merge['md'] = $sm[3];
+                               $merge['ml'] = $sm[4];
+                               if($sm[5] != '') {
+                                       $merge['hl'] = explode(',', $sm[5]);
+                               }
+                               $data['m'][] = $merge;
+                       }
+               }
+               if($matches[6] != '' && $matches[7] != '') {
+                       foreach(explode(',', $matches[7]) as $cswap) {
+                               $cswap = explode("=", $cswap);
+                               $this->color_swap[$cswap[0]] = $cswap[1];
+                       }
+               }
+               return $data;
+       }
+       
+       function image_set_color($src, $color) {
+               imagesavealpha($src, true);
+               imagealphablending($src, false);
+               // scan image pixels
+               for ($x = 0; $x < $this->size; $x++) {
+                       for ($y = 0; $y < $this->size; $y++) {
+                               $src_pix = imagecolorat($src,$x,$y);
+                               $src_pix_array = imagecolorsforindex($src, $src_pix);
+                               
+                               imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
+                       }
+               }
+       }
+
+       function overlay_image($name, $left, $color = false) {
+               $image2 = imagecreatefrompng($name);
+
+               if($color) {
+                       $this->image_set_color($image2, $color);
+               }
+               imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
+       }
+
+       function get_color($id, $text = false) {
+               if(array_key_exists($id, $this->color_swap))
+                       $id = $this->color_swap[$id];
+               $color_array = $this->colors[($id - 1) % count($this->colors)];
+               if($text && is_array($color_array[0]) && $color_array[1])
+                       return $color_array[1];
+               return (is_array($color_array[0]) ? $color_array[0] : $color_array);
+       }
+       
+       private function apply_data($data) {
+               foreach($data['l'] as $l)
+                       $this->overlay_image("img/line.png", ($l-1) * $this->size, $this->get_color($l));
+               foreach($data['m'] as $m) {
+                       if($m['dd'] == 'r')
+                               $this->overlay_image("img/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+                       else if($m['dd'] == 'l')
+                               $this->overlay_image("img/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+                       if($m['md'] == 'r')
+                               $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+                       else if($m['md'] == 'l')
+                               $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+                       if($m['ml'] == 0)
+                               $this->overlay_image("img/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+                       foreach($m['hl'] as $hl) {
+                               $this->overlay_image("img/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
+                       }
+               }
+               if($data['dot']['type'] == 'a')
+                       $this->overlay_image("img/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+               else if($data['dot']['type'] == 'b')
+                       $this->overlay_image("img/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+               else if($data['dot']['type'] == 'c')
+                       $this->overlay_image("img/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+               
+       }
+       
+}
+
+?>
\ No newline at end of file