2 /* graph.class.php - phpgitweb
3 * Copyright (C) 2011-2012 Philipp Kreil (pk910)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 class graph_data_generator {
20 const DOT_TYPE_NORMAL = 'a';
21 const DOT_TYPE_MERGE = 'b';
22 const DOT_TYPE_INIT = 'c';
24 private $data = array();
25 private $graph = array();
26 private $max_branches, $brach_id = 1, $branch_id = 1;
28 public function __construct() {
29 $this->max_branches = 10;
30 $this->data['branches'] = array();
31 $this->data['ubranches'] = array();
34 public function add_branch($first_id, $name) {
36 foreach($this->data['branches'] as &$branch) {
37 if($branch['next'] == $first_id) {
39 $branch['name'][] = $name;
46 $this->data['branches'][count($this->data['branches'])] = array(
47 "id" => $this->brach_id++,
48 "uid" => $this->branch_uid++,
51 "name" => array($name),
57 public function parse($commits) {
58 $brach_id = $this->brach_id;
59 $branch_uid = $this->branch_id;
60 $first_commit = (count($this->data['branches']) == 0 ? true : false);
61 foreach($commits as $commit) {
63 $commit['merge'] = array();
64 $commit['dot_type'] = self::DOT_TYPE_NORMAL;
66 $first_commit = false;
67 $this->data['branches'][0] = array();
68 $branch = &$this->data['branches'][0];
69 $branch['id'] = $brach_id++;
70 $branch['uid'] = $branch_uid++;
71 $branch['active'] = true;
74 foreach($this->data['branches'] as $id => &$cbranch) {
75 if($cbranch['next'] == $commit['id']) {
76 if($first && !$cbranch['pre_merge']) {
77 $branch = &$this->data['branches'][$id];
82 foreach($this->data['branches'] as $id => &$cbranch) {
83 if($cbranch['next'] == $commit['id']) {
85 $branch = &$this->data['branches'][$id];
87 } else if($cbranch['id'] == $branch['id']) {
89 $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
90 $cbranch['active'] = false;
91 if($cbranch['pre_merge']) {
92 $cbranch['pre_merge_start'] = true;
93 $cbranch['pre_merge_id'] = $branch['id'];
94 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
101 $this->data['branches'][count($this->data['branches'])] = array();
102 $branch = &$this->data['branches'][count($this->data['branches'])-1];
103 $branch['id'] = $brach_id++;
104 $branch['uid'] = $branch_uid++;
105 $branch['active'] = true;
106 $branch['pre_merge'] = false;
111 if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
113 for($j = 1; $j < count($commit['parent']); $j++) {
115 foreach($this->data['branches'] as $cbranch) {
116 if($cbranch['next'] == $commit['parent'][$j]) {
123 foreach($this->data['branches'] as $bid => &$cbranch) {
124 if(!$cbranch['active']) {
130 $this->data['branches'][count($this->data['branches'])] = array();
131 $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
132 $cbranch['id'] = $brach_id++;
134 $cbranch['uid'] = $branch_uid++;
135 $cbranch['active'] = true;
136 $cbranch['pre_merge'] = true;
137 $cbranch['next'] = $commit['parent'][$j];
139 $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
140 $commit['dot_type'] = self::DOT_TYPE_MERGE;
141 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
144 } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
145 $branch['active'] = false;
146 $commit['dot_type'] = self::DOT_TYPE_INIT;
148 $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
149 $branch['pre_merge'] = false;
150 $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
152 $commit['dot'] = $branch['id'];
154 foreach($this->data['branches'] as $id => $cbranch) {
155 $commit['branches'][$id] = $cbranch;
158 $this->graph[$commit['id']] = $this->graph_data($commit);
159 //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
163 private function graph_data($commit) {
165 $data['d'] = array();
166 $data['d']['p'] = $commit['dot']; //dot position
167 $data['d']['type'] = 'a';
168 $data['l'] = array(); //lines
169 $data['br'] = array(); //branches for color check
170 foreach($commit['branches'] as $branch) {
171 if($branch['pre_merge'] || $commit['merge']) {
172 $data['br'][] = $branch['uid'];
174 if($branch['active']) {
175 if($commit['dot'] == $branch['id']) continue;
177 if($commit['merge']) {
178 foreach($commit['merge'] as $merge) {
179 if($merge['point'] == $branch['id']) {
186 if($branch['id'] > $this->max_branches) continue;
187 $data['l'][] = $branch['id'];
190 $data['m'] = array(); //merges
191 if($commit['merge']) {
192 foreach($commit['merge'] as $merge) {
193 $mergepoint = array();
194 $mergepoint['hl'] = array();
195 $mergepoint['p'] = $merge['point'];
197 if($commit['dot'] <= $this->max_branches)
198 $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
200 $mergepoint['dd'] = 'n';
202 $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
203 if($merge['point'] <= $this->max_branches)
204 $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
206 $mergepoint['md'] = 'n';
207 $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
208 $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
209 for($i = $min; $i < $max; $i++) {
210 if($i > $this->max_branches) continue;
211 $mergepoint['hl'][] = $i;
213 $data['m'][] = $mergepoint;
216 $data['d']['type'] = $commit['dot_type'];
220 public function get_graph($id) {
221 $graph = $this->graph[$id];
222 $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
224 foreach($graph['m'] as $merge) {
227 $first_merge = false;
228 $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
229 foreach($merge['hl'] as $hline)
232 $graph['cs'] = array();
233 foreach($graph['br'] as $buid) {
234 $branch = $this->data['ubranches'][$buid];
235 if($branch['pre_merge'] && $branch['pre_merge_start'])
236 $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
238 if(count($graph['cs'])) {
239 $data .= '('.implode(',', $graph['cs']).')';
246 class graph_image_generator {
247 private $max_branches = 10;
250 private $tile_size = 20;
251 private $colors, $color_swap = array();
253 public function generate($data) {
254 $data = $this->parse_data($data);
258 $this->colors = array(
261 array(array(0, 255, 0), array(0, 192, 0)),
263 array(128, 128, 128),
269 $count = $data['count'];
270 if($count > $this->max_branches)
271 $count = $this->max_branches;
272 $this->image = imagecreatetruecolor($count * $this->size, $this->size);
273 $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
274 imagefill($this->image, 0, 0, $transparentIndex);
276 $this->apply_data($data);
278 imagecolortransparent($this->image, $transparentIndex);
280 header('Content-Type: image/png');
281 imagepng($this->image);
282 imagedestroy($this->image);
285 private function parse_data($data) {
287 if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches))
291 $data['dot'] = array();
292 $data['dot']['pos'] = $matches[1];
293 $data['dot']['type'] = $matches[2];
294 $data['count'] = $matches[3];
296 if($matches[4] != '')
297 $data['l'] = explode(',', $matches[4]);
299 $data['l'] = array();
301 $data['m'] = array();
302 if($matches[5] != '') {
303 foreach(explode('|', $matches[5]) as $m) {
306 if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
308 $merge['hl'] = array();
309 $merge['pos'] = $sm[1];
310 $merge['dd'] = $sm[2];
311 $merge['md'] = $sm[3];
312 $merge['ml'] = $sm[4];
314 $merge['hl'] = explode(',', $sm[5]);
316 $data['m'][] = $merge;
319 if($matches[6] != '' && $matches[7] != '') {
320 foreach(explode(',', $matches[7]) as $cswap) {
321 $cswap = explode("=", $cswap);
322 $this->color_swap[$cswap[0]] = $cswap[1];
328 function image_set_color($src, $color) {
329 imagesavealpha($src, true);
330 imagealphablending($src, false);
332 for ($x = 0; $x < $this->size; $x++) {
333 for ($y = 0; $y < $this->size; $y++) {
334 $src_pix = imagecolorat($src,$x,$y);
335 $src_pix_array = imagecolorsforindex($src, $src_pix);
337 imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
342 function overlay_image($name, $left, $color = false) {
343 $image2 = imagecreatefrompng($name);
346 $this->image_set_color($image2, $color);
348 imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
351 function get_color($id, $text = false) {
352 if(array_key_exists($id, $this->color_swap))
353 $id = $this->color_swap[$id];
354 $color_array = $this->colors[($id - 1) % count($this->colors)];
355 if($text && is_array($color_array[0]) && $color_array[1])
356 return $color_array[1];
357 return (is_array($color_array[0]) ? $color_array[0] : $color_array);
360 private function apply_data($data) {
361 foreach($data['l'] as $l)
362 $this->overlay_image("img/line.png", ($l-1) * $this->size, $this->get_color($l));
363 foreach($data['m'] as $m) {
365 $this->overlay_image("img/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
366 else if($m['dd'] == 'l')
367 $this->overlay_image("img/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
369 $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
370 else if($m['md'] == 'l')
371 $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
373 $this->overlay_image("img/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
374 foreach($m['hl'] as $hl) {
375 $this->overlay_image("img/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
378 if($data['dot']['type'] == 'a')
379 $this->overlay_image("img/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
380 else if($data['dot']['type'] == 'b')
381 $this->overlay_image("img/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
382 else if($data['dot']['type'] == 'c')
383 $this->overlay_image("img/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));