123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239(*s: treemap.ml *)(*s: Facebook copyright *)(* Yoann Padioleau
*
* Copyright (C) 2010 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)(*e: Facebook copyright *)openCommon2openFiguresmoduleColor=Simple_color(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*****************************************************************************)(* Types *)(*****************************************************************************)type('dir,'file)tree=('dir,'file)Common2.tree(*s: type treemap *)type('dir,'file)treemap=(treemap_data*'dir,treemap_data*'file)treeandtreemap_data={size:int;color:Simple_color.color;label:string;}(*e: type treemap *)(* with tarzan *)(*s: type algorithm *)typealgorithm=|Classic|Squarified|SquarifiedNoSort|Orderedofpivotandpivot=|PivotBySize|PivotByMiddle(*e: type algorithm *)(*s: variable algos *)letalgos=[Classic;Squarified;SquarifiedNoSort;OrderedPivotBySize;OrderedPivotByMiddle](*e: variable algos *)(*s: type screen_dim *)typescreen_dim={(* total width/height *)w:int;h:int;(* the viewport *)w_view:int;h_view:int;(* extra information *)h_status:int;w_legend:int;}(*e: type screen_dim *)(*s: type rectangle1 *)(* The array has 2 elements, for x, y. I use an array because that's how
* the seminal algorithm on treemap was written. It allows to pass
* as an int the current split and do x.(axis_split) and do a 1-axis_split
* in recursive calls to go from a x-split to a y-split.
*
* A rectangle is represented by 2 variables called P and Q in the seminal
* algorithm.
*)(*
type rectangle1 =
float array (* lower left coord, P *) *
float array (* upper right coord, Q *)
*)(*e: type rectangle1 *)(* A cleaner rectangle type, not tied to the seminal paper design decisions *)(* Now that my treemap visualizer uses a minimap, it does not completely
* use the full width.
* 16/9 = 1.777777
* 21/9 = 2.33
* I use 2510x1580 for the full codemap window, so it could be 1.58, but
* then there is a menu up and a status bar down so it should be
* higher than 1.58.
*)letxy_ratio=ref1.71(* The dimentions are in a [0.0-1.0] range for y and [0.0-xyratio] for x,
* where xyratio is used to cope with most 16/9 screens.
*)letrect_ortho()={p={x=0.0;y=0.0;};q={x=!xy_ratio;y=1.0}}(* the dimentions are in a [0.0-1.0] range
* opti? have a quad tree instead of a list, can improve search time
*)typetreemap_rendering=treemap_rectanglelistandtreemap_rectangle={tr_rect:rectangle;tr_color:int(* Simple_color.color *);tr_label:string;tr_depth:int;tr_is_node:bool;}(* with tarzan *)(*s: type layout_func *)type('a,'b)layout_func=(float*('a,'b)treemap)list->int->rectangle->(float*('a,'b)treemap*rectangle)list(*e: type layout_func *)(*****************************************************************************)(* Accessors *)(*****************************************************************************)(*s: function treemap accessors *)letcolor_of_treemap_nodex=matchxwith|Node(({color=c;_},_),_)->c|Leaf(({color=c;_},_))->cletsize_of_treemap_nodex=matchxwith|Node(({size=s;_},_),_)->s|Leaf(({size=s;_},_))->s(*e: function treemap accessors *)(*s: function algorithm accessors *)letalgo_of_salgo=matchalgowith|"classic"->Classic|"squarified"->Squarified|"squarified_no_sort"->SquarifiedNoSort|"ordered"->OrderedPivotBySize|"ordered_by_size"->OrderedPivotBySize|"ordered_by_middle"->OrderedPivotByMiddle|"default"->OrderedPivotByMiddle|_->failwith"not a valid algorithm"lets_of_algoalgo=matchalgowith|Classic->"classic"|Squarified->"squarified"|SquarifiedNoSort->"squarified_no_sort"|OrderedPivotBySize->"ordered_by_size"|OrderedPivotByMiddle->"ordered_by_middle"(*e: function algorithm accessors *)(*****************************************************************************)(* Treemap Helpers *)(*****************************************************************************)(*s: function treemap_of_tree *)lettreemap_of_tree2~size_of_leaf~color_of_leaf?(label_of_file=(fun_->""))?(label_of_dir=(fun_->""))tree=letrecauxtree=matchtreewith|Node(nodeinfo,xs)->letsizeme=ref0inletchild=List.map(funx->let(res,size)=auxxinsizeme:=!sizeme+size;res)xsin(* old:
* let children = xs +> List.map aux in
* let child = children +> List.map fst in
* let sizes = children +> List.map snd in
* let sizeme = Common.sum sizes in
*)letsizeme=!sizemeinNode(({size=sizeme;color=Color.black;(* TODO ? nodes have colors ? *)label=label_of_dirnodeinfo;},nodeinfo),child),sizeme|Leafleaf->letsizeme=size_of_leafleafinletnodeinfo=leafinLeaf(({size=sizeme;color=color_of_leafleaf;label=label_of_fileleaf;},nodeinfo)),sizemeinlet(tree,_size)=auxtreeintree(*e: function treemap_of_tree *)lettreemap_of_tree~size_of_leaf~color_of_leaf?label_of_file?label_of_dirtree=Common.profile_code"Treemap.treemap_of_tree"(fun()->treemap_of_tree2~size_of_leaf~color_of_leaf?label_of_file?label_of_dirtree)(*****************************************************************************)(* Treemap algorithms *)(*****************************************************************************)(*---------------------------------------------------------------------------*)(* basic algorithm *)(*---------------------------------------------------------------------------*)(* display_treemap and display_treemap_generic are now in
* in treemap_graphics.ml, because of Graphics dependency.
*)(*---------------------------------------------------------------------------*)(* slice and dice algorithm layout *)(*---------------------------------------------------------------------------*)(*s: layout slice and dice *)let(slice_and_dicing_layout:('a,'b)layout_func)=funchildrendepthrect->letp=[|rect.p.x;rect.p.y|]inletq=[|rect.q.x;rect.q.y|]inletaxis_split=(depth+1)mod2inletstotal=children|>List.mapfst|>Common2.sum_floatinletwidth=q.(axis_split)-.p.(axis_split)inchildren|>List.map(fun(size,child)->q.(axis_split)<-p.(axis_split)+.((size)/.stotal)*.width;letrect_here={p={x=p.(0);y=p.(1);};q={x=q.(0);y=q.(1);}}inp.(axis_split)<-q.(axis_split);size,child,rect_here)(*e: layout slice and dice *)(*---------------------------------------------------------------------------*)(* squarified algorithm *)(*---------------------------------------------------------------------------*)(*s: squarified examples *)(* ref: www.win.tue.nl/~vanwijk/stm.pdf
*
* In the following I use some of the examples in the paper so you'll need
* the paper to follow what I say.
*)(*
* A few examples.
*
* the total sum in squarified_list_area_ex is 24, just like the area
* of rect_orig below. This simplifies discussions.
*
* I've added the string later as we want squarify to also return
* information related to the node with its size (that is the full treemap
* node, with its descendant)
*)letsquarified_list_area_ex=[6;6;4;3;2;2;1]|>List.map(funx->float_of_intx,spf"info: %d"x)(* normally our algorithm should do things proportionnally to the size
* of the aready. It should not matter that the total sum of area is
* equal to the size of the rectangle. Indeed later we will always do
* things in an ortho plan, that is with a rectangle 0x0 to 1x1.
*)letsquarified_list_area_ex2=squarified_list_area_ex|>List.map(fun(x,info)->x*.2.0,info)letdim_rect_orig={p={x=0.0;y=0.0;};q={x=6.0;y=4.0}}(*e: squarified examples *)(*s: type split *)typesplit=(* Spread one next to the other, e.g. | | | | | |
* The split lines will be vertical, but the rectangles
* would be spreaded horizontally. In the paper they call that horizontal
* Split but I prefer Spread, because the split lines are actually verticals.
*)|SpreadHorizontally(* Spread one on top of the other eg _
* _
* _
*)|SpreadVertically(*e: type split *)(*s: function ratio_rect_dim *)(* we want the ratio to be a close to 1 as possible (that is to be a square) *)letratio_rect_dim(w,h)=letres=max(w/.h)(h/.w)in(* assert (res >= 1.0); *)reslet_=assert(ratio_rect_dim(6.0,4.0)=1.5)let_=assert(ratio_rect_dim(4.0,6.0)=1.5)(*e: function ratio_rect_dim *)(*s: function worst *)(* On the running example, at the first step we want to add the rect of
* size 6 on the left, alone, and its aspect ratio will be 8/3.
* Indeed its height is fixed (4) and so his width is
* whatever that must lead to an area of 6, that is 6/4 (1.5)
* which leads then to an aspect ratio of 4 vs 1.5 = 4 / 1.5 = 8/3.
* If we add 2 rect of size 6, then their aspect ratio is 1.5 which is
* better
*)letworstelems_in_rowsize_side_row=lets=Common2.sum_floatelems_in_rowinletrplus=Common2.maximumelems_in_rowinletrminus=Common2.minimumelems_in_rowin(* cf formula in paper *)max((Common2.squaresize_side_row*.rplus)/.Common2.squares)(Common2.squares/.(Common2.squaresize_side_row*.rminus))let_=assert(worst[6.0]4.0=8.0/.3.0)(* 2.66667 *)let_=assert(worst[6.0;6.0]4.0=3.0/.2.0)(* 1.5, which is close to 1 so better *)let_=assert(worst[6.0;6.0;4.0]4.0=4.0)(* 4.0, we regress *)(*e: function worst *)(*s: function layout *)(* We are given a fixed row which contains a set of elems that we have
* to spread unoformly, just like in the original algorithm.
*)letlayoutrowrect=letp=[|rect.p.x;rect.p.y|]inletq=[|rect.q.x;rect.q.y|]inletchildren=rowinletstotal=children|>List.mapfst|>Common2.sum_floatinletchildren=children|>List.map(fun(size,info)->size/.stotal(* percentage *),size,info)inletres=ref[]inletspread=ifrect_widthrect>=rect_heightrectthenSpreadHorizontallyelseSpreadVerticallyinletaxis_split=matchspreadwith|SpreadHorizontally->0|SpreadVertically->1inletwidth=q.(axis_split)-.p.(axis_split)inchildren|>List.iter(fun(percent_child,size_child,info)->q.(axis_split)<-p.(axis_split)+.percent_child*.width;letrect_here={p={x=p.(0);y=p.(1);};q={x=q.(0);y=q.(1);}}inCommon.push(size_child,info,rect_here)res;p.(axis_split)<-q.(axis_split););!res(*e: function layout *)(* the main algorithmic part of squarifying *)(*s: function squarify_orig *)letrec(squarify_orig:?verbose:bool->(float*'a)list->(float*'a)list->rectangle->(float*'a*rectangle)list)=fun?(verbose=false)childrencurrent_rowrect->(* does not work well because of float approximation.
* assert(Common.sum_float (children ++ current_row) = rect_area rect);
*)let(p,q)=rect.p,rect.qinletfloatsxs=List.mapfstxsin(* First heuristic in the squarified paper *)letspread=ifrect_widthrect>=rect_heightrect(* e.g. 6 x 4 rectangle *)thenSpreadHorizontallyelseSpreadVerticallyin(* We now know what kind of row we want. If spread horizontally then
* we will have a row on the left to fill and the size of the side of
* this row is known and is the height of the rectangle (in our ex 4).
* In the paper they call this variable 'width' but it's misleading.
* Note that because we are in Horizontal mode, inside this left row,
* things will be spreaded this time vertically.
*)letsize_side_row=matchspreadwith|SpreadHorizontally->rect_heightrect|SpreadVertically->rect_widthrectinmatchchildrenwith|c::cs->ifnullcurrent_row||(worst(floats(current_row@[c]))size_side_row)<=(worst(floatscurrent_row)size_side_row)then(* not yet optimal row, let's recurse *)squarify_origcs(current_row@[c])rectelsebegin(* optimal layout for the left row. We can fix it. *)letsrow=Common2.sum_float(floatscurrent_row)inletstotal=Common2.sum_float(floats(current_row@children))inletportion_for_row=srow/.stotalinletrow_rect,remaining_rect=matchspreadwith|SpreadHorizontally->letmiddle_x=(q.x-.p.x)*.portion_for_row+.p.xin{p=p;q={x=middle_x;y=q.y};},{p={x=middle_x;y=p.y};q=q;}|SpreadVertically->letmiddle_y=(q.y-.p.y)*.portion_for_row+.p.yin{p=p;q={x=q.x;y=middle_y;};},{p={x=p.x;y=middle_y};q=q;}inifverbosethenbeginpr2"layoutrow:";pr2_gencurrent_row;pr2"row rect";pr2(s_of_rectanglerow_rect);end;letrects_row=layoutcurrent_rowrow_rectinletrects_remain=squarify_origchildren[]remaining_rectinrects_row@rects_remainend|[]->ifverbosethenbeginpr2"layoutrow:";pr2_gencurrent_row;pr2"row rect";pr2(s_of_rectanglerect);end;layoutcurrent_rowrect(*e: function squarify_orig *)(*s: function squarify *)letsquarifychildrenrect=(* squarify_orig assume the sum of children = area rect *)letarea=rect_arearectinlettotal=Common2.sum_float(List.mapfstchildren)inletchildren'=children|>List.map(fun(x,info)->(x/.total)*.area,info)insquarify_origchildren'[]rect(*e: function squarify *)(*s: function test_squarify *)lettest_squarify()=pr2_gen(worst[6.0]4.0);pr2_gen(worst[6.0;6.0]4.0);pr2_gen(worst[6.0;6.0;4.0]4.0);pr2_xxxxxxxxxxxxxxxxx();squarifysquarified_list_area_exdim_rect_orig|>ignore;pr2_xxxxxxxxxxxxxxxxx();squarifysquarified_list_area_ex2(rect_ortho())|>ignore;()(*e: function test_squarify *)(*s: layout squarify *)let(squarify_layout:('a,'b)layout_func)=funchildren_depthrect->letchildren'=children|>Common.sort_by_key_highfirstinsquarifychildren'rectlet(squarify_layout_no_sort_size:('a,'b)layout_func)=funchildren_depthrect->squarifychildrenrect(*e: layout squarify *)(*---------------------------------------------------------------------------*)(* Ordered squarified algorithm *)(*---------------------------------------------------------------------------*)(*s: ordered examples *)(* ref:
*)letchildren_ex_ordered_2001=[1;5;3;4;5;1;10;1;1;2;7;3;5;2;10;1;2;1;1;2;](*e: ordered examples *)(*s: type pivotized *)type'apivotized={left:'a;right:'a;pivot:'a;(* this one should be singleton and the other a list *)above_pivot:'a;}(*e: type pivotized *)(*s: function compute_rects_pivotized *)letcompute_rects_pivotizedchilds_pivotizedrectspread=let(p,q)=rect.p,rect.qinletx=childs_pivotizedinletsize={left=Common2.sum_float(List.mapfstx.left);right=Common2.sum_float(List.mapfstx.right);pivot=Common2.sum_float(List.mapfstx.pivot);above_pivot=Common2.sum_float(List.mapfstx.above_pivot);}inlettotal_size=size.left+.size.right+.size.pivot+.size.above_pivotinletportion_for_left=size.left/.total_sizeinletportion_for_right=size.right/.total_sizeinletportion_for_pivot_vs_above=(size.pivot)/.(size.pivot+.size.above_pivot)in(* computing the rectangle of the left and right is easy as the
* height is fixed (when we spread horizontally)
*)matchspreadwith|SpreadHorizontally->(* TODO do something that adapt to rect ? lourd que rect
* commence pas 0,0, ca fait faire des calculs en plus. *)letmiddle_x1=p.x+.((rect_widthrect)*.portion_for_left)inletmiddle_x2=q.x-.((rect_widthrect)*.portion_for_right)inletmiddle_y=p.y+.((rect_heightrect)*.portion_for_pivot_vs_above)in{left={p=p;q={x=middle_x1;y=q.y}};right={p={x=middle_x2;y=p.y};q=q;};pivot={p={x=middle_x1;y=p.y};q={x=middle_x2;y=middle_y};};above_pivot={p={x=middle_x1;y=middle_y};q={x=middle_x2;y=q.y;}};}|SpreadVertically->(* just the reverse of previous code, x become y and vice versa *)letmiddle_y1=p.y+.((rect_heightrect)*.portion_for_left)inletmiddle_y2=q.y-.((rect_heightrect)*.portion_for_right)inletmiddle_x=p.x+.((rect_widthrect)*.portion_for_pivot_vs_above)in{left={p=p;q={x=q.x;y=middle_y1;}};right={p={x=p.x;y=middle_y2;};q=q;};pivot={p={x=p.x;y=middle_y1;};q={x=middle_x;y=middle_y2;}};above_pivot={p={x=middle_x;y=middle_y1;};q={x=q.x;y=middle_y2;}}}(*e: function compute_rects_pivotized *)(*s: function balayer_right_wrong *)(*
let rec balayer_right_wrong xs =
match xs with
| [] -> []
| x::xs ->
let first =
[], x::xs
in
let last =
x::xs, []
in
let rest = balayer_right_wrong xs in
let rest' = rest +> List.map (fun (start, theend) -> x::start, theend) in
[first] ++ rest' ++ [last]
*)letbalayer_rightxs=letn=List.lengthxsinletres=ref[]infori=0tondoCommon.push(takeixs,dropixs)res;done;List.rev!reslet_=assert(balayer_right[1;2;3;2]=[[],[1;2;3;2];[1],[2;3;2];[1;2],[3;2];[1;2;3],[2];[1;2;3;2],[];])(*e: function balayer_right_wrong *)(*s: function orderify_children *)letorderify_children?(pivotf=PivotBySize)xsrect=letrecauxxsrect=matchxswith|[]->[]|[size,x]->[size,x,rect]|_x::_y::_ys->letleft,pivot,right=matchpivotfwith|PivotBySize->letpivot_max=Common2.maximum(xs|>List.mapfst)inCommon2.split_when(funx->fstx=pivot_max)xs|PivotByMiddle->letnmiddle=List.lengthxs/2inletstart,thend=Common2.splitAtnmiddlexsinstart,List.hdthend,List.tlthendinletspread=ifrect_widthrect>=rect_heightrect(* e.g. 6 x 4 rectangle *)thenSpreadHorizontallyelseSpreadVerticallyinletright_combinations=balayer_rightrightinletscores_and_rects=right_combinations|>List.map(fun(above_pivot,right)->letchilds_pivotized={left=left;pivot=[pivot];right=right;above_pivot=above_pivot;}inletrects=compute_rects_pivotizedchilds_pivotizedrectspreadinratio_rect_dim(rect_widthrects.pivot,rect_heightrects.pivot),(rects,childs_pivotized))inletbest=Common.sort_by_key_lowfirstscores_and_rects|>List.hdinlet(_score,(rects,childs_pivotized))=bestin(* pr2_gen rects; *)auxchilds_pivotized.leftrects.left@auxchilds_pivotized.pivotrects.pivot@auxchilds_pivotized.above_pivotrects.above_pivot@auxchilds_pivotized.rightrects.right@[]inauxxsrect(*e: function orderify_children *)(*s: function test_orderify *)lettest_orderify()=letxs=children_ex_ordered_2001|>List.mapfloat_of_intinletrect=rect_ortho()inletfake_treemap=()inletchildren=xs|>List.map(funsize->size,fake_treemap)inletlayout=orderify_childrenchildrenrectinpr2_genlayout(*e: function test_orderify *)(*s: layout ordered *)let(ordered_layout:?pivotf:pivot->('a,'b)layout_func)=fun?pivotfchildren_depthTODOMAYBErect->orderify_children?pivotfchildrenrect(*e: layout ordered *)(*---------------------------------------------------------------------------*)(* cushion algorithm *)(*---------------------------------------------------------------------------*)(* TODO *)(*---------------------------------------------------------------------------*)(* frontend *)(*---------------------------------------------------------------------------*)letlayoutf_of_algoalgo=matchalgowith|Classic->slice_and_dicing_layout|Squarified->squarify_layout|SquarifiedNoSort->squarify_layout_no_sort_size|Orderedpivotf->ordered_layout~pivotfletrender_treemap_algo2=fun?(algo=Classic)?(big_borders=false)treemap->letflayout=layoutf_of_algoalgoinlettreemap_rects=ref[]inletrecaux_treemaprootrect~depth=let(p,q)=rect.p,rect.qinifnot(valid_rectrect)then()(* TODO ? warning ? *)else(matchrootwith|Leaf(tnode,_fileinfo)->letcolor=color_of_treemap_noderootinCommon.push{tr_rect=rect;tr_color=color;tr_label=tnode.label;tr_depth=depth;tr_is_node=false;}treemap_rects;|Node(mode,children)->(* let's draw some borders. Far better to see the structure. *)Common.push{tr_rect=rect;tr_color=Color.black;tr_label=(fstmode).label;tr_depth=depth;tr_is_node=true;}treemap_rects;(* does not work, weird *)letborder=ifnotbig_bordersthenmatchdepthwith|1->0.0|2->0.003|3->0.001|4->0.0005|5->0.0002|_->0.0elsematchdepthwith|1->0.0|2->0.003|3->0.0015|4->0.0010|5->0.0008|6->0.0005|_->0.0002inletp={x=p.x+.border;y=p.y+.border;}inletq={x=q.x-.border;y=q.y-.border;}in(* todo? can overflow ... check still inside previous rect *)letrect={p=p;q=q}inletchildren'=children|>List.map(funchild->float_of_int(size_of_treemap_nodechild),child)inletrects_with_info=(* generic call *)flayoutchildren'depthrectin(* less: assert rects_with_info are inside rect ? *)rects_with_info|>List.iter(fun(_x,child,rect)->aux_treemapchildrect~depth:(depth+1));)inaux_treemaptreemap(rect_ortho())~depth:1;List.rev!treemap_rectsletrender_treemap?algo?big_bordersx=Common.profile_code"Treemap.render_treemap"(fun()->render_treemap_algo2?algo?big_bordersx)(*****************************************************************************)(* Main display function *)(*****************************************************************************)(* now in treemap_graphics.ml *)(*****************************************************************************)(* Source converters *)(*****************************************************************************)typedirectory_sort=|NoSort|SortDirThenFiles|SortDirAndFiles|SortDirAndFilesCaseInsensitiveletfollow_symlinks=reffalse(*s: function tree_of_dir *)(*
let tree_of_dir2
?(filter_file=(fun _ -> true))
?(filter_dir=(fun _ -> true))
?(sort=SortDirAndFilesCaseInsensitive)
~file_hook
dir
=
let rec aux dir =
let subdirs =
Common2.readdir_to_dir_list dir +> List.map (Filename.concat dir) in
let files =
Common2.readdir_to_file_list dir +> List.map (Filename.concat dir) in
let subdirs =
subdirs +> Common.map_filter (fun dir ->
if filter_dir dir
then Some (dir, aux dir)
else None
)
in
let files =
files +> Common.map_filter (fun file ->
if filter_file file
then Some (file, (Leaf (file, file_hook file)))
else None
)
in
let agglomerated =
match sort with
| NoSort -> subdirs ++ files
| SortDirThenFiles ->
Common.sort_by_key_lowfirst subdirs ++
Common.sort_by_key_lowfirst files
| SortDirAndFiles ->
Common.sort_by_key_lowfirst (subdirs ++ files)
| SortDirAndFilesCaseInsensitive ->
let xs = (subdirs ++ files) +> List.map (fun (s, x) ->
lowercase s, x
)
in
Common.sort_by_key_lowfirst xs
in
let children = List.map snd agglomerated in
Node(dir, children)
in
aux dir
*)(*e: function tree_of_dir *)(* specialized version *)lettree_of_dir3?(filter_file=(fun_->true))?(filter_dir=(fun_->true))?(sort=SortDirAndFilesCaseInsensitive)~file_hookdir=ifsort<>SortDirAndFilesCaseInsensitivethenfailwith"Only SortDirAndFilesCaseInsensitive is handled";letrecauxdir=letchildren=Sys.readdirdirinletchildren=Array.map(funx->String.lowercase_asciix,x)childreninArray.fast_sort(fun(a1,_b1)(a2,_b2)->comparea1a2)children;letres=ref[]inchildren|>Array.iter(fun(_,f)->letfull=Filename.concatdirfinletstat=Common2.unix_lstat_efffullinmatchstat.Unix.st_kindwith|Unix.S_REG->iffilter_filefullthenCommon.push(Leaf(full,file_hookfull))res|Unix.S_DIR->iffilter_dirfullthenCommon.push(auxfull)res|Unix.S_LNK->if!follow_symlinksthen(try(match(Unix.statfull).Unix.st_kindwith|Unix.S_REG->iffilter_filefullthenCommon.push(Leaf(full,file_hookfull))res|Unix.S_DIR->iffilter_dirfullthenCommon.push(auxfull)res|_->())withUnix.Unix_error_->pr2(spf"PB stat link at %s"full);)else()|_->());Node(dir,List.rev!res)inauxdirlettree_of_dir?filter_file?filter_dir?sort~file_hooka=Common.profile_code"Treemap.tree_of_dir"(fun()->tree_of_dir3?filter_file?filter_dir?sort~file_hooka)lettree_of_dir_or_file?filter_file?filter_dir?sort~file_hookpath=ifCommon2.is_directorypaththentree_of_dir?filter_file?filter_dir?sort~file_hookpathelseLeaf(path,file_hookpath)(* Some nodes may have stuff in common that we should factor.
* todo: factorize code with Common.tree_of_files
*)letadd_intermediate_nodesroot_pathnodes=letroot=chop_dirsymbolroot_pathinifnot(Common2.is_absoluteroot)thenfailwith("must pass absolute path, not: "^root);letroot=Common.split"/"rootin(* extract dirs and file from file, e.g. ["home";"pad"], "__flib.php", path *)letxs=nodes|>List.map(funx->matchxwith|Leaf(file,_)->Common2.dirs_and_base_of_filefile,x|Node(dir,_)->Common2.dirs_and_base_of_filedir,x)in(* remove the root part *)letxs=xs|>List.map(fun((dirs,base),node)->letn=List.lengthrootinlet(root',rest)=Common2.takendirs,Common2.dropndirsinassert(root'=*=root);(rest,base),node)in(* now ready to build the tree recursively *)letrecauxcurrent_rootxs=letfiles_here,rest=xs|>List.partition(fun((dirs,_base),_)->nulldirs)inletgroups=rest|>group_by_mapped_key(fun((dirs,_base),_)->(* would be a file if null dirs *)assert(not(nulldirs));List.hddirs)inletnodes=groups|>List.map(fun(k,xs)->letxs'=xs|>List.map(fun((dirs,base),node)->(List.tldirs,base),node)inletdirname=Filename.concatcurrent_rootkinNode(dirname,auxdirnamexs'))inletleaves=files_here|>List.map(fun((_dir,_base),node)->node)innodes@leavesinauxroot_pathxslettree_of_dirs_or_files2?filter_file?filter_dir?sort~file_hookpaths=matchpathswith|[]->failwith"tree_of_dirs_or_files: empty list"|[x]->tree_of_dir_or_file?filter_file?filter_dir?sort~file_hookx|xs->letnodes=xs|>List.map(funx->tree_of_dir_or_file?filter_file?filter_dir?sort~file_hookx)inletroot=Common2.common_prefix_of_files_or_dirsxsinletnodes=add_intermediate_nodesrootnodesinNode(root,nodes)lettree_of_dirs_or_files?filter_file?filter_dir?sort~file_hookx=Common.profile_code"Treemap.tree_of_dirs_or_files"(fun()->tree_of_dirs_or_files2?filter_file?filter_dir?sort~file_hookx)(* Some software, especially java have often a long chain
* of single directory, like org/eclipse/...
* which then introduce extra depth in the treemap which leads
* to overlapping labels and very small labels for the actual
* childrens. This function removes those intermediate singleton
* sub directories.
*)letrecremove_singleton_subdirstree=matchtreewith|Leaf_x->tree|Node(x,[Node(_y,ys)])->(* todo? merge x and y ? *)remove_singleton_subdirs(Node(x,ys))|Node(x,ys)->Node(x,List.mapremove_singleton_subdirsys)(*****************************************************************************)(* Testing *)(*****************************************************************************)(*s: concrete rectangles example *)(* src: python treemap.py
* lower, upper, rgb
*)lettreemap_rectangles_ex=[[0.0,0.0],[1.0,1.0],(0.17778372236496054,0.75183542244426871,0.77892130219255096);[0.0,0.0],[0.27659574468085107,1.0],(0.54757582213226441,0.945582381819014,0.26427761420055917);[0.0,0.0],[0.27659574468085107,0.38461538461538464],(0.71931501307446211,0.95905644995588246,0.28633110533256656);[0.0,0.38461538461538464],[0.27659574468085107,1.0],(0.29508972521695809,0.35521829137775873,0.46070336222733932);[0.0,0.38461538461538464],[0.10372340425531915,1.0],(0.51529552034735771,0.53725734991812635,0.22430742368105949);[0.10372340425531915,0.38461538461538464],[0.27659574468085107,1.0],(0.43861905319415506,0.16281118710897469,0.60250203640050937);[0.27659574468085107,0.0],[0.36170212765957449,1.0],(0.3743827201120038,0.07170428778373239,0.09006244270341246);[0.36170212765957449,0.0],[0.8936170212765957,1.0],(0.39117531981521536,0.16579633978705666,0.63690597944460248);[0.36170212765957449,0.0],[0.8936170212765957,0.20000000000000001],(0.34982099039431447,0.54618822154424429,0.19282777912183513);[0.36170212765957449,0.20000000000000001],[0.8936170212765957,0.28000000000000003],(0.14570785913376116,0.88033416430670342,0.51911403487550056);[0.36170212765957449,0.28000000000000003],[0.8936170212765957,0.76000000000000001],(0.79691567717907263,0.3307536109585284,0.95607296382731199);[0.36170212765957449,0.28000000000000003],[0.45035460992907805,0.76000000000000001],(0.7038680786604008,0.12714028216462059,0.17131117338368551);[0.45035460992907805,0.28000000000000003],[0.58333333333333337,0.76000000000000001],(0.036414279679915174,0.94100891978030599,0.017007582879843386);[0.58333333333333337,0.28000000000000003],[0.8936170212765957,0.76000000000000001],(0.63659306932350279,0.25303150185397794,0.81066700006123815);[0.58333333333333337,0.28000000000000003],[0.8936170212765957,0.48571428571428577],(0.38368601825375115,0.083946154840038423,0.048274714595522017);[0.58333333333333337,0.48571428571428577],[0.8936170212765957,0.62285714285714289],(0.70513207607633877,0.95785105976069096,0.87735329563400943);[0.58333333333333337,0.62285714285714289],[0.8936170212765957,0.76000000000000001],(0.80565735169264896,0.75578523763882166,0.10757369310766951);[0.36170212765957449,0.76000000000000001],[0.8936170212765957,1.0],(0.57042872206220896,0.9335301149492965,0.86254084187238389);[0.36170212765957449,0.76000000000000001],[0.62765957446808507,1.0],(0.31530318311042171,0.97066142447913661,0.93180609525183578);[0.62765957446808507,0.76000000000000001],[0.8936170212765957,1.0],(0.18330061581424317,0.82234170300788867,0.38303955663618716);[0.8936170212765957,0.0],[1.0,1.0],(0.20641218447120302,0.35715481613716149,0.86620796882602547);[0.8936170212765957,0.0],[1.0,0.59999999999999998],(0.7942020522649591,0.27351921049542915,0.86191731793444748);[0.8936170212765957,0.59999999999999998],[1.0,1.0],(0.27214488578650742,0.41635201268319189,0.1301335726270938);](*e: concrete rectangles example *)(*s: variable tree_ex_shneiderman_1991 *)lettree_ex_shneiderman_1991=letninfo=()inNode(ninfo,[Leaf12;Leaf6;Node(ninfo,[Leaf2;Leaf2;Leaf2;Leaf2;Leaf2;]);Node(ninfo,[Node(ninfo,[Leaf5;Leaf20;]);Node(ninfo,[Leaf5;]);Leaf40;]);])(*e: variable tree_ex_shneiderman_1991 *)(*s: variable tree_ex_wijk_1999 *)lettree_ex_wijk_1999=letninfo=()inNode(ninfo,[Leaf6;Leaf6;Leaf4;Leaf3;Leaf2;Leaf2;Leaf1;])(*e: variable tree_ex_wijk_1999 *)(*s: variable treemap_ex_ordered_2001 *)let(treemap_ex_ordered_2001:(unit,unit)treemap)=letchildren=children_ex_ordered_2001inletchildren_treemap=children|>Common.index_list_1|>List.map(fun(size,i)->Leaf({size=size;color=Color.color_of_string(spf"grey%d"(90-(i*3)));label=spf"size = %d"size;},()))inlettotal_size=Common2.sumchildreninNode(({size=total_size;color=Color.black;label="";},()),children_treemap)(*e: variable treemap_ex_ordered_2001 *)(*****************************************************************************)(* Actions *)(*****************************************************************************)letactions()=[(*s: treemap actions *)"-test_squarify","<>",Common.mk_action_0_arg(test_squarify);"-test_orderify","<>",Common.mk_action_0_arg(test_orderify);(*e: treemap actions *)](*e: treemap.ml *)