HudsonのJOBのネットワーク図がほしい

前日のエントリ(id:sikakura:20100803)の最後で、
「依存関係はhttp://XX.XX.XX.XX:9999/hudson/view/ビュー名/job/ジョブ名/configureでないと取れない」
と書いたのですが、http://XX.XX.XX.XX:9999/hudson/view/ビュー名/job/ジョブ名/api/jsonのdownstreamProjectsとupstreamProjectsで取れることが分かりました。
そうとなればJOBのネットワーク図が欲しくなるわけで、最初のたたき台を作成してみました。
最初のたたき台+「動けばいいや」的なものですので、いろいろと(?)ご了承ください^^

<html>
  <head>
    <title>Canvas tutorial</title>
    <script type="text/javascript" src="jquery-1.3.2.min.js"></script>
    <script type="text/javascript">
    
    var childlist = [];
    var count = 0;
    var manager = new JobManager();

    function loading(){
        $.ajax({
            async:false,
            dataType: 'jsonp',
            jsonp: 'jsonp',
            url: 'http://XX.XX.XX.XX:9999/hudson/view/all/api/json?',
            error: function(){
                //データの読み込みに失敗
                alert("JSONPの読み込みに失敗しました");
            },
            success: function(json){
                $.each(json.jobs, function(i,item){
                    var obj = new HudsonJob(item.name);
                    manager.regist( obj );
                });
            },
            complete: function(){
                for( var i in manager.jobs ){
                    child( manager.jobs[i].name, manager.jobs.length );
                }
            }
        });
    }
    
    function child(name,size){
        $.ajax({
            async:false,
            dataType: 'jsonp',
            jsonp: 'jsonp',
            url: 'http://XX.XX.XX.XX:9999/hudson/view/all/job/' + name + '/api/json?',
            error: function(){
                //データの読み込みに失敗
                alert("JSONPの読み込みに失敗しました");
            },
            success: function(json){
                $.each(json.downstreamProjects, function(i,item){
                    childlist.push( { parent:name, child:item.name } );
                });
            },
            complete:function(){
                count++;
                if( count == size ){
                    var localChild = childlist;
                    for( var j in localChild ){
                        var pp = localChild[j].parent;
                        var cc = localChild[j].child;
                        manager.registChild(manager.find(pp), manager.find(cc));
                    }
                    
                    for( var i in manager.jobs ){
                        manager.jobs[i].draw();
                        manager.jobs[i].connect();
                        manager.jobs[i].displayName();
                    }
                }
            }
        });
    }

    function JobManager(){
        var x_margin = 25;
        var y_margin = 25;
        this.jobs = [];
        this.level = 0;
        this.regist = function(obj){
            obj.x = x_margin;
            obj.y = this.level * (obj.h + y_margin) + y_margin;
            this.jobs.push( obj );
            this.level++;
        }
        this.registChild = function(parent,child){
            for( var i in this.jobs ){
                if( this.jobs[i].name == parent.name ){
                    this.jobs[i].child.push( child );
                    child.parent.push( this.jobs[i] );
                    child.x = this.jobs[i].x + child.w + x_margin;
                    child.y = this.jobs[i].y + (this.jobs[i].child.length-1) * (child.h + y_margin);
                    if( this.jobs[i].child.length == 1 ){
                        this.levelUp( this.jobs[i] );
                    }
                    return;
                }
            }
        }
        this.find = function(name){
            for( var i in this.jobs ){
                if( this.jobs[i].name == name ) return this.jobs[i];
            }
            return null;
        }
        this.levelUp = function(obj){
            var nextX = obj.x;
            var nextY = obj.y + obj.h + y_margin;
            for( var i in this.jobs ){
                if( this.jobs[i].y > nextY ){
                    this.jobs[i].y = this.jobs[i].y - obj.h - y_margin;
                }
            }
        }
    }
    function HudsonJob(name){
        this.name = name;
        this.x = 0;
        this.y = 0;
        this.w = 100;
        this.h = 80;
        this.parent = [];
        this.child = [];
        this.connect = function(){
            if( this.child.length != 0){
                var canvas = document.getElementById('tutorial');
                if (canvas.getContext){
                    var ctx = canvas.getContext('2d');

                    for( var i in this.child ){
                        ctx.moveTo(this.x+this.w,this.y+this.h/2);
                        if( this.x < this.child[i].x ){
                            if( this.y <= this.child[i].y ){
                                ctx.lineTo(this.x+this.w+(this.child[i].x-this.x-this.w),this.y+this.h/2+(this.child[i].y-this.y));
                                ctx.lineTo(this.x+this.w+(this.child[i].x-this.x-this.w)-10,this.y+this.h/2+(this.child[i].y-this.y)-10);
                            }
                        }else{
                            ctx.lineTo(this.x+this.w+(this.x-this.child[i].x-this.w),this.y+this.h/2+(this.child[i].y-this.y));
                            ctx.lineTo(this.x+this.w+(this.x-this.child[i].x-this.w)-10,this.y+this.h/2+(this.child[i].y-this.y)-10);
                        }
                        ctx.stroke();
                    }
                }
            }
        }
        this.draw = function(){
            var canvas = document.getElementById('tutorial');
            if (canvas.getContext){
                var ctx = canvas.getContext('2d');
                ctx.strokeRect(this.x, this.y, this.w, this.h);
            }
        }
        this.displayName = function(){
            var div = document.createElement('div');
            div.innerHTML = this.name;
            div.style.left = this.x + 10;
            div.style.top = this.y + 10;
            div.style.position = "absolute";
            var objBody = document.getElementsByTagName("body").item(0);
            objBody.appendChild(div);
        }
    }
    </script>
    <style type="text/css">
      canvas { border: 1px solid blue; }
    </style>
  </head>
  <body onload="loading();">
    <canvas id="tutorial" width="1024px" height="1786px" ></canvas>
  </body>
</html>

見た目はこんな感じになります。

いろいろとJavascirptでネットワーク図的なものを簡単に描画可能なライブラリなどを探したのですが、なかなか見つかりませんでした。よって、HTML5CANVASを利用しました。このままだとIEで動作しないとのことですので、ExplorerCanvas(http://code.google.com/p/explorercanvas/downloads/list)のお世話になるのもよいかと思います。
HTML5については、以下を参考にしました。

こういったHudsonのネットワーク図を表示するプラグインがすでにあったりするような気もしますが、どうなんでしょうかね。
追記:色を付けてみました。