Author: tim.bunce
Date: Mon Apr 6 13:34:49 2009
New Revision: 738
Added:
trunk/lib/Devel/NYTProf/js/jquery-treemap.js
Modified:
trunk/bin/nytprofhtml
trunk/lib/Devel/NYTProf/Data.pm
Log:
Initial work-in-progress using jquery treemap plugin. Quite rough at the
moment.
Checking in for-the-record as I'm going to go experiment with
http://blog.thejit.org/javascript-information-visualization-toolkit-jit/
next
Modified: trunk/bin/nytprofhtml
==============================================================================
--- trunk/bin/nytprofhtml (original)
+++ trunk/bin/nytprofhtml Mon Apr 6 13:34:49 2009
@@ -266,21 +266,81 @@
}
+# http://www.jquery.info/The-TreeMap-plugin
+#
sub package_tables {
my ($profile) = @_;
+ # XXX may not be appropriate if profiling wasn't continuous
+ my $profiler_duration = $profile->{attribute}{profiler_duration};
+
+ # [
+ # undef, # depth 0
+ # { # depth 1
+ # "main::" => [ [ subinfo1, subinfo2 ] ], # 2 subs in 1 pkg
+ # "Foo::" => [ [ subinfo3 ], [ subinfo4 ] ] # 2 subs in 2 pkg
+ # }
+ # { # depth 2
+ # "Foo::Bar::" => [ [ subinfo3 ] ] # 1 sub in 1 pkg
+ # "Foo::Baz::" => [ [ subinfo4 ] ] # 1 sub in 1 pkg
+ # }
+ # ]
my $pkg_depth = $profile->packages_at_depth_subinfo({
include_unused_subs => 0,
rollup_packages => 1,
merge_subinfos => 1,
});
+ # default:
+ # { pkgname => [ subinfo1, subinfo2, ... ], ... }
+ # merged:
+ # { pkgname => [ single_merged_subinfo ], ... }
my $package_subinfo_map = $profile->package_subinfo_map(1);
- # XXX may not be appropriate if profiling wasn't continuous
- my $profiler_duration = $profile->{attribute}{profiler_duration};
+ local $Data::Dumper::Sortkeys = 1;
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Maxdepth = 0;
+ #die Data::Dumper::Dumper($profile->package_subinfo_map(1,1));
+ my $package_tree_subinfo_map = $profile->package_subinfo_map(1,1);
+ use JSON::Any;
+sub h2a {
+ my ($h, $t) = @_;
+ local $_;
+ return [ map {
+ my $v = $h->{$_};
+ (ref $v eq 'HASH')
+ ? [ $_, h2a($v, ($t) ? "$t\::$_" : $_) ]
+ : [ $t, $v->[0]->excl_time ]
+ } keys %$h ];
+};
+ my $ary = h2a($package_tree_subinfo_map, "");
+ #die Data::Dumper::Dumper($ary);
+ my $json = JSON::Any->new->objToJson($ary);
+ #die $json;
my $pkg_html;
+ $pkg_html .= qq{
+ <div><div id="tm">\nXXX\n</div></div><br>
+ };
+
+push @on_ready_js, qq{
+ function getTreemapData(el) {
+ return $json;
+ }
+ jQuery("div#tm").treemap(800,600,{
+ getData: getTreemapData,
+ colorDiscrete: false,
+ headHeight: 12,
+ target: "div#tm",
+ sort: true
+ });
+ jQuery("div#tm div.treemapCell").hover(
+ function(){jQuery(this).addClass("selected")},
+ function(){jQuery(this).removeClass("selected")}
+ );
+};
+
+
# generate a separate table for each depth
for my $depth (0...@$pkg_depth-1) {
@@ -971,6 +1031,17 @@
<link rel="stylesheet" type="text/css" href="style.css"></link>
<title>$title</title>
<script type="text/javascript" src="jquery-min.js"></script>
+
+ <script type="text/javascript" src="jquery-treemap.js"></script>
+ <style type="text/css">
+ .treemapView { background-color:#dddddd }
+ .treemapSquare {}
+ .treemapHead { font-size:10px; background:none }
+ .treemapCell { font-size:8px; font-style:italic }
+ .treemapCell.selected, .treemapCell.selected .treemapCell.selected
{background-color:#ff9999}
+ .treemapCell.selected .treemapCell
{background-color:#ffff99}
+ </style>
+
<script type="text/javascript"
src="jquery-tablesorter-min.js"></script>
<link rel="stylesheet" type="text/css"
href="style-tablesorter.css"></link>
<script type="text/javascript">
Modified: trunk/lib/Devel/NYTProf/Data.pm
==============================================================================
--- trunk/lib/Devel/NYTProf/Data.pm (original)
+++ trunk/lib/Devel/NYTProf/Data.pm Mon Apr 6 13:34:49 2009
@@ -111,13 +111,11 @@
return { %{ shift->{sub_subinfo} } }; # shallow copy
}
-# default:
# { pkgname => [ subinfo1, subinfo2, ... ], ... }
-# merged:
-# { pkgname => [ single_merged_subinfo ], ... }
-sub package_subinfo_map {
+# if merged is true then array contains a single 'merged' subinfo
+sub XXXpackage_subinfo_map {
my $self = shift;
- my ($merged) = @_;
+ my ($merged_subs, $nested_pkgs) = @_;
my $all_subs = $self->subname_subinfo_map;
my %pkg;
@@ -125,7 +123,7 @@
$name =~ s/^(.*::).*/$1/; # XXX $subinfo->package
push @{ $pkg{$name} }, $subinfo;
}
- if ($merged) {
+ if ($merged_subs) {
while ( my ($pkg_name, $subinfos) = each %pkg ) {
my $subinfo = shift(@$subinfos)->clone;
$subinfo->merge_in($_) for @$subinfos;
@@ -133,6 +131,45 @@
@$subinfos = ($subinfo);
}
}
+ return \%pkg;
+}
+
+# package_tree_subinfo_map is like package_subinfo_map but returns
+# nested data instead of flattened.
+# for "Foo::Bar::Baz" package:
+# { Foo => { '' => [...], '::Bar' => { ''=>[...], '::Baz'=>[...] } } }
+# if merged is true then array contains a single 'merged' subinfo
+sub package_subinfo_map {
+ my $self = shift;
+ my ($merge_subs, $nested_pkgs) = @_;
+
+ my %pkg;
+ my %to_merge;
+
+ my $all_subs = $self->subname_subinfo_map;
+ while ( my ($name, $subinfo) = each %$all_subs ) {
+ $name =~ s/^(.*::).*/$1/; # XXX $subinfo->package
+ my $subinfos;
+ if ($nested_pkgs) {
+ my @parts = split /::/, $name;
+ my $node = $pkg{ shift @parts } ||= {};
+ $node = $node->{ shift @parts } ||= {} while @parts;
+ $subinfos = $node->{''} ||= [];
+ }
+ else {
+ $subinfos = $pkg{$name} ||= [];
+ }
+ push @$subinfos, $subinfo;
+ $to_merge{$subinfos} = $subinfos if $merge_subs;
+ }
+
+ for my $subinfos (values %to_merge) {
+ my $subinfo = shift(@$subinfos)->clone;
+ $subinfo->merge_in($_) for @$subinfos;
+ # replace the many with the one
+ @$subinfos = ($subinfo);
+ }
+
return \%pkg;
}
Added: trunk/lib/Devel/NYTProf/js/jquery-treemap.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/jquery-treemap.js Mon Apr 6 13:34:49 2009
@@ -0,0 +1,303 @@
+/*
+* treemap plugin for jQuery (version 1.0.3 13/8/2008)
+* Copyright (c) 2007-2008 Renato Formato <[email protected]>
+* Dual licensed under the MIT (MIT-LICENSE.txt)
+* and GPL (GPL-LICENSE.txt) licenses.
+*/
+(function($) {
+$.fn.treemap = function(w,h,options) {
+ options =
$.extend({labelCell:0,dataCell:1,colorCell:2,headHeight:20,borderWidth:1,sort:true,nested:false,legend:false},options);
+ var or_target = options.target;
+ return this.pushStack($.map(this,function(el){
+ var data;
+ if(!options.getData) {
+ if(!$.nodeName(el,"table")) return;
+ data = treemap.getDataFromTable(el,options);
+ } else {
+ data = options.getData(el);
+ }
+
+ //copy data because during the processing elements are deleted
+ data = data.concat();
+
+ if($.fn.treemap.caller!=treemap.layoutRow) {
+ options.minColorValue = Number.POSITIVE_INFINITY;
+ options.maxColorValue = Number.NEGATIVE_INFINITY;
+ if(!options.colorDiscreteVal) options.colorDiscreteVal
= {num:0};
+ treemap.normalizeValues(data,options);
+ options.colorDiscrete = options.minColorValue ==
Number.POSITIVE_INFINITY;
+ options.rangeColorValue =
options.maxColorValue-options.minColorValue;
+ }
+
+ if (options.sort)
+ data.sort(function(a,b){
+ var val1 = b[1], val2 = a[1];
+ val1 =
val1.constructor==Array?treemap.getValue(val1):val1;
+ val2 =
val2.constructor==Array?treemap.getValue(val2):val2;
+ return val1-val2;
+ });
+
+ options.target = or_target || el;
+ options.numSquare = 0;
+
+ treemap.render(data,h,w,options);
+
+ if($.fn.treemap.caller!=treemap.layoutRow && options.legend) {
+
jQuery(options.target).append(treemap.legend(h,options));
+ }
+
+ if(options.target==el && $.nodeName(el,"table")) {
+ var newObj = jQuery(el).find(">").insertBefore(el);
+ $(el).remove();
+ el = newObj.get();
+ }
+ return el;
+ }));
+}
+
+$.fn.treemapClone = function() {
+ return this.pushStack( jQuery.map( this, function(a){
+ return a.outerHTML ? jQuery(a.outerHTML)[0] : a.cloneNode(true);
+ }));
+}
+
+$.fn.treemapAppend = function(arguments) {
+ var el = this[0];
+ for(var i=0,l=arguments.length;i<l;i++)
+ el.appendChild(arguments[i]);
+ return this;
+}
+
+
+var treemap = {
+ normalizeValues : function(data,options) {
+ for(var i=0,dl=data.length;i<dl;i++)
+ if(data[i][1].constructor==Array)
+ treemap.normalizeValues(data[i][1],options);
+ else {
+ var val = data[i][1] = parseFloat(data[i][1]);
+ var color = data[i][2];
+ if(color<options.minColorValue)
options.minColorValue=color;
+ if(color>options.maxColorValue)
options.maxColorValue=color;
+ if(!options.colorDiscreteVal[color])
options.colorDiscreteVal[color] =
options.colorDiscreteVal.num++;
+ }
+ },
+
+ getDataFromTable : function(table,options) {
+ var data = [];
+ if(options.labelCell==undefined) options.labelCell =
options.dataCell;
+ $("tbody tr",table).each(function(){
+ var cells = $(">",this);
+ var row = [cells.eq(options.labelCell).html(),
+
cells.eq(options.dataCell).html(),
+
cells.eq(options.colorCell).html()];
+ data.push(row);
+ });
+ return data;
+ },
+
+ emptyView: $("<div>").addClass("treemapView"),
+
+ render : function(data,h,w,options) {
+ options.height = h;
+ options.width = w;
+ var s = treemap.calculateArea(data);
+ options.viewAreaCoeff = w*h/s;
+ options.view =
treemap.emptyView.clone().css({'width':w,'height':h});
+ options.content = [];
+ treemap.squarify(data,[],h,true,options);
+ options.view.treemapAppend(options.content);
+ $(options.target).empty().treemapAppend(options.view);
+ },
+
+ squarify : function(data,row,w,orientation,options) {
+ if(w<=0) return; //exit if there's no space left on the treemap
+ var widerRow = row,s,s2,current;
+ do {
+ row = widerRow;
+ s = treemap.calculateArea(row);
+ if(data.length==0) return
treemap.layoutRow(row,w,orientation,s,options,true);
+ current = data.shift();
+ widerRow = row.concat();
+ widerRow.push(current);
+ s2 =
s+(current[1].constructor==Array?treemap.getValue(current[1]):current[1]);
+ } while
(treemap.worst(row,w,s,options.viewAreaCoeff)>=treemap.worst(widerRow,w,s2,options.viewAreaCoeff))
+
+ var rowDim = treemap.layoutRow(row,w,orientation,s,options);
+ data.unshift(current);
+
+ if(!rowDim) rowDim =
treemap.layoutRow([['',s]],w,orientation,s,options,true);
+ var width;
+ if(orientation) {
+ options.width -= rowDim;
+ width = options.width;
+ } else {
+ options.height -= rowDim;
+ width = options.height;
+ }
+ treemap.squarify(data,[],width,!orientation,options);
+ },
+
+ worst : function(row,w,s,coeff) {
+ var rl = row.length;
+ if(!rl) return Number.POSITIVE_INFINITY;
+ var w2 = w*w, s2 = s*s*coeff;
+ var r1 =
(w2*(row[0][1].constructor==Array?treemap.getValue(row[0][1]):row[0][1]))/s2;
+ var r2 =
s2/(w2*(row[rl-1][1].constructor==Array?treemap.getValue(row[rl-1][1]):row[rl-1][1]));
+ return Math.max( r1, r2 );
+ },
+
+ emptyCell:
$("<div>").addClass("treemapCell").css({'float':'left','overflow':'hidden'}),
+ emptySquare: $("<div>").addClass("treemapSquare").css('float','left'),
+
+ layoutRow : function(row,w,orientation,s,options,last) {
+ var square = treemap.emptySquare.treemapClone();
+ var rowDim, h = s/w;
+ if(orientation) {
+ rowDim =
last?options.width:Math.min(Math.round(h*options.viewAreaCoeff),options.width);
+
square.css({'width':rowDim,'height':w}).addClass("treemapV");
+ } else {
+ rowDim =
last?options.height:Math.min(Math.round(h*options.viewAreaCoeff),options.height);
+
square.css({'height':rowDim,'width':w}).addClass("treemapH");
+ }
+ var rl = row.length-1,sum = 0, bw = options.borderWidth, bw2 =
bw*2,
cells = [];
+ for(var i=0;i<=rl;i++) {
+ var n = row[i],hier = n[1].constructor == Array, head =
[], val =
hier?treemap.getValue(n[1]):n[1];
+ var cell = treemap.emptyCell.treemapClone();
+ if(!hier) cell.append(n[0])[0].title = cell.text()+'
('+val+')';
+ var lastCell = i==rl;
+ var fixedDim = rowDim, varDim = lastCell ? w-sum :
Math.round(val/h);
+ if(varDim<=0) break;
+ sum += varDim;
+ var cellStyles = {};
+ if(bw && rowDim>bw2 && varDim>bw2) {
+ if(jQuery.boxModel) {
+ fixedDim -= bw*(2-(options.numSquare>=2
|| !options.numSquare &&
options.nested?1:0)-(last && options.nested?1:0));
+ varDim -=
bw*(2-(!lastCell||options.nested?1:0)-(options.numSquare>=1
&& !i?1:0));
+ }
+ cellStyles.border = bw+'px solid';
+ if(!lastCell || options.nested)
+
cellStyles['border'+(orientation?'Bottom':'Right')] = 'none';
+ if(options.numSquare>=2 || !options.numSquare
&& options.nested)
+
cellStyles['border'+(orientation?'Left':'Top')] = 'none';
+ if(options.numSquare>=1 && !i)
+
cellStyles['border'+(orientation?'Top':'Left')] = 'none';
+ if(last && options.nested)
+
cellStyles['border'+(orientation?'Right':'Bottom')] = 'none';
+ }
+ var height = orientation?varDim:fixedDim, width =
orientation?fixedDim:varDim;
+
+ cellStyles.height = height+'px';
+ cellStyles.width = width+'px';
+ if(hier) {
+ if(options.headHeight) {
+ head = $("<div
class='treemapHead'>").css({"width":width,"height":options.headHeight,"overflow":"hidden"}).html(n[0]).attr('title',n[0]+'
('+val+')');
+ if(orientation)
+ height = varDim -=
options.headHeight;
+ else
+ height = fixedDim -=
options.headHeight;
+
+ }
+ if(height>0) {
+ var new_opt = {};
+ for(var prop in options) new_opt[prop]
= options[prop];
+ new_opt["target"] = null;
+ new_opt =
jQuery.extend(new_opt,{getData:function(){return
n[1].concat()},nested:true});
+ cell.treemap(width,height,new_opt);
+ }
+ cell.prepend(head);
+ } else {
+ if(n[2]) cellStyles.backgroundColor =
treemap.getColor(n[2],options);
+ }
+
+ //cell.css(cellStyles);
+ var cellstyle = cell[0].style;
+ for(var prop in cellStyles)
+ cellstyle[prop] = cellStyles[prop];
+
+ cells.push(cell[0]);
+ }
+ options.content.push(square.treemapAppend(cells)[0]);
+ options.numSquare++;
+ return rowDim;
+ },
+
+ calculateArea : function(row) {
+ if(row.total) return row.total;
+ var s = 0,rl = row.length;
+ for(var i=0;i<rl;i++) {
+ var val = row[i][1];
+ s += val.constructor==Array?treemap.getValue(val):val;
+ }
+
+ return row.total = s;
+ },
+
+ getValue : function(val) {
+ if(!val.total) val.total=treemap.calculateArea(val);
+ return val.total;
+ },
+
+ getColor : function(val,options) {
+ var colorCode;
+ if(options.colorDiscrete) {
+ colorCode =
options.colorDiscreteVal[val]/options.colorDiscreteVal.num;
+ } else {
+ colorCode =
(val-options.minColorValue)/options.rangeColorValue;
+ }
+ return treemap.getColorCode(colorCode);
+ },
+
+ getColorCode : function(colorCode) {
+ colorCode = Math.round(colorCode*510);
+ if(colorCode==0) return "#0000FF";
+ if(colorCode<=255) {
+ var code1 = colorCode.toString(16);
+ if(code1.length<2) code1 = "0"+code1;
+ var code2 = (255-colorCode).toString(16);
+ if(code2.length<2) code2 = "0"+code2;
+ return "#00"+code1+code2;
+ }
+ if(colorCode<=510) {
+ colorCode -= 255
+ var code1 = (colorCode).toString(16);
+ if(code1.length<2) code1 = "0"+code1;
+ var code2 = (255-colorCode).toString(16);
+ if(code2.length<2) code2 = "0"+code2;
+ return "#"+code1+code2+"00";
+ }
+ },
+
+ emptyLegendDescr : $("<div
class='treemapLegendDescr'>").css({position:'absolute',left:25,width:200}),
+
+ legend : function(h,options) {
+ var l = $("<div
class='treemapLegend'>").css({position:'relative','float':'left',height:h-2});
+ var bar = $("<div>").css({width:20,height:h-2,border:"1px
solid"});
+ options.view.css({'float':'left','marginRight':20});
+ if(options.colorDiscrete) {
+ $.each(options.colorDiscreteVal,function(i,n){
+ if(i!='num') {
+ i = options.descriptionCallback ?
options.descriptionCallback(i):i;
+ var height =
Math.round(n*h/options.colorDiscreteVal.num);
+ var bar =
$("<div>").css({height:20,width:20,backgroundColor:treemap.getColor(i,options),position:'absolute',bottom:height});
+ var desc =
treemap.emptyLegendDescr.clone().text(i).css('bottom',height);
+ l.append(bar).append(desc);
+ }
+ });
+ } else {
+ for(var i=h-1;i>1;i--) {
+ var color =
$("<div>").height(1).css("backgroundColor",treemap.getColorCode(i/h));
+ bar.append(color);
+ };
+ l.append(bar);
+ for(var i=0;i<10;i++) {
+ var val =
i*options.rangeColorValue/10+options.minColorValue;
+ val = options.descriptionCallback ?
options.descriptionCallback(val):val;
+ var desc =
treemap.emptyLegendDescr.clone().text(val.toString()).css('bottom',Math.round(i*h/10));
+ l.append(desc);
+ };
+ }
+ return l;
+ }
+}
+})(jQuery)
--~--~---------~--~----~------------~-------~--~----~
You've received this message because you are subscribed to
the Devel::NYTProf Development User group.
Group hosted at: http://groups.google.com/group/develnytprof-dev
Project hosted at: http://perl-devel-nytprof.googlecode.com
CPAN distribution: http://search.cpan.org/dist/Devel-NYTProf
To post, email: [email protected]
To unsubscribe, email: [email protected]
-~----------~----~----~----~------~----~------~--~---