# added a new LOC graph by file in the code folder view.

# added D3.js 3.5.5 and CodeFlower.js
This commit is contained in:
hyung-hwan 2015-04-23 09:25:26 +00:00
parent ca598a238e
commit 5d41e1739d
9 changed files with 338 additions and 78 deletions

View File

@ -229,5 +229,7 @@ LICENSE
CLOC 1.62 GPL
Flot https://github.com/flot/flot/blob/master/LICENSE.txt
Font Awesome 4.3.0 MIT & SIL OFL 1.1
D3.js 3.5.5 BSD
CodeFlower MIT
------------------------------------------------------------------------

View File

@ -70,7 +70,7 @@ class Graph extends Controller
}
}
function history_json ($projectid = '', $path = '')
function enjson_code_history ($projectid = '', $path = '')
{
$this->load->model ('ProjectModel', 'projects');
@ -113,7 +113,7 @@ class Graph extends Controller
print codepot_json_encode ($history);
}
function folder_loc_json ($projectid = '', $path = '', $rev = SVN_REVISION_HEAD)
function enjson_loc_by_lang ($projectid = '', $path = '', $rev = SVN_REVISION_HEAD)
{
$this->load->model ('ProjectModel', 'projects');
@ -137,7 +137,36 @@ class Graph extends Controller
if ($path == '.') $path = ''; /* treat a period specially */
$path = $this->_normalize_path ($path);
$cloc = $this->subversion->clocRev ($projectid, $path, $rev);
$cloc = $this->subversion->clocRevByLang ($projectid, $path, $rev);
print codepot_json_encode ($cloc);
}
function enjson_loc_by_file ($projectid = '', $path = '', $rev = SVN_REVISION_HEAD)
{
$this->load->model ('ProjectModel', 'projects');
$login = $this->login->getUser ();
if (CODEPOT_SIGNIN_COMPULSORY && $login['id'] == '')
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
return;
}
$project = $this->projects->get ($projectid);
if ($project === FALSE || ($project->public !== 'Y' && $login['id'] == ''))
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
return;
}
$this->load->model ('SubversionModel', 'subversion');
$path = $this->converter->HexToAscii ($path);
if ($path == '.') $path = ''; /* treat a period specially */
$path = $this->_normalize_path ($path);
$cloc = $cloc = $this->subversion->clocRevByFile ($projectid, $path, $rev);
print codepot_json_encode ($cloc);
}
}

View File

@ -944,8 +944,7 @@ class SubversionModel extends Model
return @svn_proplist ($workurl, 0, $rev);
}
function _cloc_revision ($projectid, $path, $rev)
function _cloc_revision_by_lang ($projectid, $path, $rev)
{
$orgurl = 'file://'.$this->_canonical_path(CODEPOT_SVNREPO_DIR."/{$projectid}/{$path}");
@ -982,7 +981,7 @@ class SubversionModel extends Model
$actual_tfname = $tfname . '.d';
codepot_delete_files ($actual_tfname, TRUE); // delete the directory in case it exists
if (svn_checkout ($workurl, $actual_tfname, $rev, 0) === FALSE)
if (@svn_checkout ($workurl, $actual_tfname, $rev, 0) === FALSE)
{
codepot_delete_files ($actual_tfname, TRUE);
@unlink ($tfname);
@ -1021,55 +1020,99 @@ class SubversionModel extends Model
return $cloc_data;
}
function clocRev ($projectid, $path, $rev)
function clocRevByLang ($projectid, $path, $rev)
{
return $this->_cloc_revision ($projectid, $path, $rev);
return $this->_cloc_revision_by_lang ($projectid, $path, $rev);
}
function clocRevBySubdir ($projectid, $path, $rev)
function clocRevByFile ($projectid, $path, $rev)
{
$orgurl = 'file://'.$this->_canonical_path(CODEPOT_SVNREPO_DIR."/{$projectid}/{$path}");
// this function composes the data as CodeFlower requires
$workurl = ($path == '')? $orgurl: "{$orgurl}@"; // trailing @ for collision prevention
$info = @svn_info ($workurl, FALSE, $rev);
if ($info === FALSE || count($info) != 1)
$stack = array();
$cloc = new stdClass();
$cloc->name = basename($path);
if ($cloc->name == '') $cloc->name = '/';
$cloc->children = array();
array_push ($stack, $path);
array_push ($stack, $cloc);
while (!empty($stack))
{
// If a URL is at the root of repository and the url type is file://
// (e.g. file:///svnrepo/codepot/ where /svnrepo/codepot is a project root),
// some versions of libsvn end up with an assertion failure like
// ... libvvn_subr/path.c:114: svn_path_join: Assertion `svn_path_is_canonical(base, pool)' failed.
//
// Since the root directory is guaranteed to exist at the head revision,
// the information can be acquired without using a peg revision.
// In this case, a normal operational revision is used to work around
// the assertion failure. Other functions that has to deal with
// the root directory should implement this check to work around it.
//
if ($rev == SVN_REVISION_HEAD || $path == '') return FALSE;
$current_cloc = array_pop($stack);
$current_path = array_pop($stack);
// rebuild the URL with a peg revision and retry it.
$workurl = "{$orgurl}@{$rev}";
$orgurl = 'file://'.$this->_canonical_path(CODEPOT_SVNREPO_DIR."/{$projectid}/{$current_path}");
$trailer = ($current_path == '')? '': '@'; // trailing @ for collision prevention
$workurl = $orgurl . $trailer;
$info = @svn_info ($workurl, FALSE, $rev);
if ($info === FALSE || count($info) != 1) return FALSE;
}
if ($info[0]['kind'] == SVN_NODE_FILE) return FALSE;
$lsinfo = @svn_ls ($workurl, $rev, FALSE, TRUE);
$cloc_data = array ();
foreach ($lsinfo as $key => $value)
{
if ($value['type'] == 'dir')
if ($info === FALSE || count($info) != 1)
{
$cloc = $this->_cloc_revision ($projectid, "{$path}/{$key}", $rev);
if ($cloc === FALSE) return FALSE;
// If a URL is at the root of repository and the url type is file://
// (e.g. file:///svnrepo/codepot/ where /svnrepo/codepot is a project root),
// some versions of libsvn end up with an assertion failure like
// ... libvvn_subr/path.c:114: svn_path_join: Assertion `svn_path_is_canonical(base, pool)' failed.
//
// Since the root directory is guaranteed to exist at the head revision,
// the information can be acquired without using a peg revision.
// In this case, a normal operational revision is used to work around
// the assertion failure. Other functions that has to deal with
// the root directory should implement this check to work around it.
//
if ($rev == SVN_REVISION_HEAD || $path == '') continue;
$cloc_data[$key] = $cloc;
// rebuild the URL with a peg revision and retry it.
$trailer = "@{$rev}";
$workurl = $orgurl . $trailer;
$info = @svn_info ($workurl, FALSE, $rev);
if ($info === FALSE || count($info) != 1) continue;
}
if ($info[0]['kind'] == SVN_NODE_FILE) return FALSE;
$info0 = &$info[0];
$list = @svn_ls ($workurl, $rev, FALSE, TRUE);
if ($list === FALSE) return FALSE;
foreach ($list as $key => $value)
{
$full_path = $current_path . '/' . $key;
if ($value['type'] == 'file')
{
$obj = new stdClass();
$obj->name = $key;
$text = @svn_cat ("{$orgurl}/{$key}{$trailer}", $rev);
if ($text === FALSE) $obj->size = 0;
else
{
$text_len = strlen($text);
$obj->size = substr_count($text, "\n");
if ($text_len > 0 && $text[$text_len - 1] != "\n") $obj->size++;
}
$obj->language = substr(strrchr($key, '.'), 1); // file extension
if ($obj->language === FALSE) $obj->language = '';
array_push ($current_cloc->children, $obj);
}
else
{
$obj = new stdClass();
$obj->name = $key;
$obj->children = array();
array_push ($current_cloc->children, $obj);
array_push ($stack, $full_path);
array_push ($stack, $obj);
}
}
}
return $cloc_data;
return $cloc;
}
function zipSubdir ($projectid, $path, $rev, $topdir)

View File

@ -30,6 +30,8 @@
<script type="text/javascript" src="<?php print base_url_make('/js/jquery.flot.stack.min.js')?>"></script>
<script type="text/javascript" src="<?php print base_url_make('/js/jquery.flot.tickrotor.js')?>"></script>
<script type="text/javascript" src="<?php print base_url_make('/js/d3.min.js')?>"></script>
<script type="text/javascript" src="<?php print base_url_make('/js/CodeFlower.js')?>"></script>
<?php
$file_count = count($file['content']);
@ -62,7 +64,7 @@ function show_tooltip(id, x, y, contents) {
}).appendTo("body").fadeIn(200);
}
function show_loc_graph (response)
function show_loc_by_lang_graph (response)
{
var loc = $.parseJSON(response);
if (loc == null)
@ -114,32 +116,48 @@ function show_loc_graph (response)
yaxes: { }
};
$("#code_folder_mainarea_result_info_loc_graph").width(550).height(400);
$.plot($("#code_folder_mainarea_result_info_loc_graph"), dataset, options);
$("#code_folder_mainarea_result_info_loc_by_lang_graph").width(550).height(400);
$.plot($("#code_folder_mainarea_result_info_loc_by_lang_graph"), dataset, options);
var code_folder_mainarea_result_info_loc_by_lang_graph_previous_point = null;
var code_folder_mainarea_result_info_loc_graph_previous_point = null;
$("#code_folder_mainarea_result_info_loc_graph").bind("plothover", function (event, pos, item) {
$("#code_folder_mainarea_result_info_loc_by_lang_graph").bind("plothover", function (event, pos, item) {
if (item)
{
if (code_folder_mainarea_result_info_loc_graph_previous_point != item.datapoint)
if (code_folder_mainarea_result_info_loc_by_lang_graph_previous_point != item.datapoint)
{
code_folder_mainarea_result_info_loc_graph_previous_point = item.datapoint;
$("#code_folder_mainarea_result_info_loc_graph_tooltip").remove();
show_tooltip("code_folder_mainarea_result_info_loc_graph_tooltip", item.pageX, item.pageY - 20, item.datapoint[1]);
code_folder_mainarea_result_info_loc_by_lang_graph_previous_point = item.datapoint;
$("#code_folder_mainarea_result_info_loc_by_lang_graph_tooltip").remove();
show_tooltip("code_folder_mainarea_result_info_loc_by_lang_graph_tooltip", item.pageX, item.pageY - 20, item.datapoint[1]);
}
}
else
{
$("#code_folder_mainarea_result_info_loc_graph_tooltip").remove();
code_folder_mainarea_result_info_loc_graph_previous_point = null;
$("#code_folder_mainarea_result_info_loc_by_lang_graph_tooltip").remove();
code_folder_mainarea_result_info_loc_by_lang_graph_previous_point = null;
}
});
}
$("#code_folder_mainarea_result_info_loc_button").button("enable");
//$("#code_folder_mainarea_result_info_loc_progress" ).progressbar().hide();
$("#code_folder_mainarea_result_info_loc_by_lang_button").button("enable");
$("#code_folder_mainarea_result_info_loc_by_lang_spin" ).removeClass ("fa-cog fa-spin");
}
function show_loc_by_file_graph (response)
{
var loc = $.parseJSON(response);
if (loc == null)
{
alert ('Invalid data received');
}
else
{
var f = new CodeFlower("#code_folder_mainarea_result_info_loc_by_file_graph", 550, 400);
f.update (loc);
}
$("#code_folder_mainarea_result_info_loc_by_file_button").button("enable");
$("#code_folder_mainarea_result_info_loc_by_file_spin" ).removeClass ("fa-cog fa-spin");
}
function render_readme()
@ -188,28 +206,30 @@ $(function () {
}
});
btn = $("#code_folder_mainarea_result_info_loc_button").button().click (function () {
$("#code_folder_mainarea_result_info_loc_button").button("disable");
//$("#code_folder_mainarea_result_info_loc_progress" ).progressbar("value", 0).show();
//function show_progress ()
//{
// var progress = $("#code_folder_mainarea_result_info_loc_progress");
// progress.progressbar ("value", progress.progressbar("value") + 1);
// setTimeout (show_progress, 1000);
//}
//setTimeout (show_progress, 1000);
btn = $("#code_folder_mainarea_result_info_loc_by_lang_button").button().click (function () {
$("#code_folder_mainarea_result_info_loc_by_lang_button").button("disable");
$("#code_folder_mainarea_result_info_loc_by_lang_spin").addClass ("fa-cog fa-spin");
var ajax_req = $.ajax ({
url: codepot_merge_path (
"<?php print site_url(); ?>",
"/graph/folder_loc_json/<?php print $project->id; ?>/<?php print $this->converter->AsciiToHex($headpath)?><?php print $revreq?>"),
"/graph/enjson_loc_by_lang/<?php print $project->id; ?>/<?php print $this->converter->AsciiToHex($headpath)?><?php print $revreq?>"),
context: document.body,
success: show_loc_graph
success: show_loc_by_lang_graph
});
});
//$("#code_folder_mainarea_result_info_loc_progress" ).progressbar().hide();
btn = $("#code_folder_mainarea_result_info_loc_by_file_button").button().click (function () {
$("#code_folder_mainarea_result_info_loc_by_file_button").button("disable");
$("#code_folder_mainarea_result_info_loc_by_file_spin").addClass ("fa-cog fa-spin");
var ajax_req = $.ajax ({
url: codepot_merge_path (
"<?php print site_url(); ?>",
"/graph/enjson_loc_by_file/<?php print $project->id; ?>/<?php print $this->converter->AsciiToHex($headpath)?><?php print $revreq?>"),
context: document.body,
success: show_loc_by_file_graph
});
});
<?php endif; ?>
$('#code_search_submit').button().click (function () {
@ -426,9 +446,8 @@ $this->load->view (
<div class="result" id="code_folder_mainarea_result">
<div id="code_folder_mainarea_result_info_loc_progress"></div>
<div id="code_folder_mainarea_result_info_loc_graph">
</div>
<div id="code_folder_mainarea_result_info_loc_by_lang_graph"></div>
<div id="code_folder_mainarea_result_info_loc_by_file_graph"></div>
<?php
function comp_files ($a, $b)
@ -624,8 +643,16 @@ $this->load->view (
print '<div class="title">LOC</div>';
print '<a id="code_folder_mainarea_result_info_loc_button" href="#">';
print $this->lang->line('Graph');
print '<a id="code_folder_mainarea_result_info_loc_by_lang_button" href="#">';
print '<i id="code_folder_mainarea_result_info_loc_by_lang_spin" class="fa"></i>';
print $this->lang->line('Language');
print '</a>';
print ' ';
print '<a id="code_folder_mainarea_result_info_loc_by_file_button" href="#">';
print '<i id="code_folder_mainarea_result_info_loc_by_file_spin" class="fa"></i>';
print $this->lang->line('File');
print '</a>';
print '</div>';

View File

@ -413,7 +413,7 @@ function show_all_graphs (response)
function render_graphs()
{
var ajax_req = $.ajax ({
url: '<?php print site_url()?>/graph/history_json/<?php print $project->id?>/',
url: '<?php print site_url()?>/graph/enjson_code_history/<?php print $project->id?>/',
context: document.body,
success: show_all_graphs
});

View File

@ -0,0 +1,150 @@
var CodeFlower = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
this.svg.append("svg:rect")
// .style("stroke", "#999999")
.style("fill", "#FFFFFF")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.on("tick", this.tick.bind(this))
.charge(function(d) { return d._children ? -d.size / 100 : -40; })
.linkDistance(function(d) { return d.target._children ? 80 : 25; })
.size([h, w]);
};
CodeFlower.prototype.update = function(json) {
if (json) this.json = json;
this.json.fixed = true;
this.json.x = this.w / 2;
this.json.y = this.h / 2;
var nodes = this.flatten(this.json);
var links = d3.layout.tree().links(nodes);
var total = nodes.length || 1;
// remove existing text (will readd it afterwards to be sure it's on top)
this.svg.selectAll("text").remove();
// Restart the force layout
this.force
.gravity(Math.atan(total / 50) / Math.PI * 0.4)
.nodes(nodes)
.links(links)
.start();
// Update the links
this.link = this.svg.selectAll("line.link")
.data(links, function(d) { return d.target.name; });
// Enter any new links
this.link.enter().insert("svg:line", ".node")
.attr("class", "link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// Exit any old links.
this.link.exit().remove();
// Update the nodes
this.node = this.svg.selectAll("circle.node")
.data(nodes, function(d) { return d.name; })
.classed("collapsed", function(d) { return d._children ? 1 : 0; });
this.node.transition()
.attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; });
// Enter any new nodes
this.node.enter().append('svg:circle')
.attr("class", "node")
.classed('directory', function(d) { return (d._children || d.children) ? 1 : 0; })
.attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; })
.style("fill", function color(d) {
return "hsl(" + parseInt(360 / total * d.id, 10) + ",90%,70%)";
})
.call(this.force.drag)
.on("click", this.click.bind(this))
.on("mouseover", this.mouseover.bind(this))
.on("mouseout", this.mouseout.bind(this));
// Exit any old nodes
this.node.exit().remove();
this.text = this.svg.append('svg:text')
.attr('class', 'nodetext')
.attr('dy', 0)
.attr('dx', 0)
.attr('text-anchor', 'middle');
return this;
};
CodeFlower.prototype.flatten = function(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) {
node.size = node.children.reduce(function(p, v) {
return p + recurse(v);
}, 0);
}
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
};
CodeFlower.prototype.click = function(d) {
// Toggle children on click.
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update();
};
CodeFlower.prototype.mouseover = function(d) {
this.text.attr('transform', 'translate(' + d.x + ',' + (d.y - 5 - (d.children ? 3.5 : Math.sqrt(d.size) / 2)) + ')')
.text(d.name + ": " + d.size)
.style('display', null)
.style('font-size', '0.9em');
};
CodeFlower.prototype.mouseout = function(d) {
this.text.style('display', 'none');
};
CodeFlower.prototype.tick = function() {
var h = this.h;
var w = this.w;
this.link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
this.node.attr("transform", function(d) {
return "translate(" + Math.max(5, Math.min(w - 5, d.x)) + "," + Math.max(5, Math.min(h - 5, d.y)) + ")";
});
};
CodeFlower.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};

View File

@ -22,7 +22,9 @@ www_DATA = \
jquery.flot.threshold.min.js \
jquery.flot.time.min.js \
excanvas.min.js \
jquery.flot.tickrotor.js
jquery.flot.tickrotor.js \
d3.min.js \
CodeFlower.js
EXTRA_DIST = $(www_DATA)

View File

@ -260,7 +260,9 @@ www_DATA = \
jquery.flot.threshold.min.js \
jquery.flot.time.min.js \
excanvas.min.js \
jquery.flot.tickrotor.js
jquery.flot.tickrotor.js \
d3.min.js \
CodeFlower.js
EXTRA_DIST = $(www_DATA)
all: all-recursive

5
codepot/src/js/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long