Index: openacs-4/packages/workflow/tcl/graph-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/graph-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/tcl/graph-procs.tcl 4 Jan 2007 09:06:53 -0000 1.2 @@ -0,0 +1,131 @@ +ad_library { + Procedures in the workflow::graph namespace. + + @creation-date 5 April 2005 + @author jmhek@cs.ucla.edu + @cvs-id $Id: graph-procs.tcl,v 1.2 2007/01/04 09:06:53 avnik Exp $ +} + +namespace eval workflow::graph {} + +ad_proc -public workflow::graph::draw { + {-workflow_id:required} + {-filename ""} + {-highlight ""} + {-options_array_name ""} +} { + This procedure is used to generate .dot file of graphviz + @param workflow_id + @param filename + @param highlight A list of states to highlight, the first element in the list is the current state, all other elements are previous states. + @param options_array_name the string name of the hash of additional options. Keys are: + include_subject_count: If this option has a value of 1, then include the subject count by state. + subject_term_pl : the plural pretty name of the subject type (rats, tissues, subjects, etc) + subject_term : singular pretty name of the subject type (rat, tissue, subject, etc) + @return 0 if success, o.w. 1 +} { + # set filename + set path [acs_package_root_dir workflow] + append path /www/admin/graph + + if {![empty_string_p $options_array_name]} { + upvar $options_array_name options + } + + #check to see what options were passed in, init things that aren't there + set available_options [list "include_subject_count" "subject_term" "subject_term_pl"] + set option_names [array names options] + + foreach opt $available_options { + + #default the value + if {[lsearch $option_names $opt] == -1} { + set options($opt) "" + } + } + + + #exchange some defaults for more descriptive values + if {[string equal $options(subject_term) ""]} { + set options(subject_term) "subject" + set options(subject_term_pl) "subjects" + } + + + if {[empty_string_p $filename]} { + set filename workflow_$workflow_id + } + + set current_state "" + set previous_state "" + if {![empty_string_p $highlight]} { + set current_state [lindex $highlight 0] + set previous_state [lreplace $highlight 0 0] + } + + set dot "digraph workflow_$workflow_id \{\n" + #append dot " graph \[size=\"16,16\"\];\n" + append dot " node \[fontname=\"Courier\", color=lightblue2, style=filled\];\n" + append dot " edge \[fontname=\"Courier\"\];\n" + + set states [workflow::fsm::get_states -workflow_id $workflow_id] + + #get the subject counts for the workflow + if {$options(include_subject_count) == 1} { + ptracker::subject::get_subject_count_in_workflow -workflow_id $workflow_id\ + -array_name "subjects_in_workflow" + } + + foreach state_id $states { + workflow::state::fsm::get -state_id $state_id -array "state_info" + + set num_subjects_in_state "" + if {[array exists subjects_in_workflow]} { + set subject_count 0 + if {[lsearch [array names subjects_in_workflow] $state_id] != -1} { + set subject_count $subjects_in_workflow($state_id) + } + + set descriptor $options(subject_term_pl) + if {$subject_count == 1} { + set descriptor $options(subject_term) + } + set num_subjects_in_state "($subject_count $descriptor)" + } + + if {$state_id == $current_state} { + append dot " state_$state_id \[label=\"$state_info(pretty_name) $num_subjects_in_state\", color=darkorange1\];\n" + } elseif {[lsearch $previous_state $state_id]!=-1} { + append dot " state_$state_id \[label=\"$state_info(pretty_name) $num_subjects_in_state\", color=steelblue3\];\n" + } else { + append dot " state_$state_id \[label=\"$state_info(pretty_name) $num_subjects_in_state\"\];\n" + } + } + + set actions [workflow::action::fsm::get_ids -workflow_id $workflow_id] + foreach action_id $actions { + workflow::action::get -action_id $action_id -array "action_info" + + if {![empty_string_p $action_info(new_state)]} { + if {![empty_string_p $action_info(assigned_states)]} { + foreach x $action_info(assigned_state_ids) { + append dot " state_$x -> state_$action_info(new_state_id) \[label=\"$action_info(pretty_name)\"\];\n" + } + } + + if {![empty_string_p $action_info(enabled_states)]} { + foreach x $action_info(enabled_state_ids) { + append dot " state_$x -> state_$action_info(new_state_id) \[label=\"$action_info(pretty_name)\"\];\n" + } + } + } + } + append dot "\}\n" + + set flag [catch { + template::util::write_file $path/$filename\.dot $dot + exec /usr/bin/dot -Tjpg $path/$filename\.dot -o $path/$filename\.jpg + } errmsg] + + return $flag +} Index: openacs-4/packages/workflow/tcl/workflow-tree-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/workflow/tcl/workflow-tree-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/workflow/tcl/workflow-tree-procs.tcl 4 Jan 2007 09:06:53 -0000 1.2 @@ -0,0 +1,232 @@ +ad_library { + Procs for building trees of data. + + @author helsleya@cs.ucr.edu + @creation-date 2004/09/27 + @cvs-id $Id: workflow-tree-procs.tcl,v 1.2 2007/01/04 09:06:53 avnik Exp $ + + @tree::sorter::create + @tree::sorter::make_full_key_for + @tree::sorter::sort +} + +namespace eval workflow::tree::sorter {} + +ad_proc -public workflow::tree::sorter::create { + {-multirow:required} + {-sort_by:required} +} { +

Initiliaze a bunch of state variables for a connect by ordered + tree.

+ +

Example:

+

+    # Create a 'sorter' to sort the data
+    tree::sorter::create -multirow categories_tree -sort_by sort_key
+    
+    # Get the data
+    db_multirow -extend {sort_key} categories_tree categories_tree_sql {
+	select  lpad(' ', 6*4*(level-1) + 1, ' ') || name as name,
+	category_id,
+	name as rawname,
+	level
+	from  categories
+	connect by prior category_id    = parent_category_id
+	start with parent_category_id   = category.lookup('//Personnel Title')
+    } {
+	set sort_key [tree::sorter::make_full_key_for      \
+			  -multirow     categories_tree  \
+			  -partial_key  $rawname         \
+			  -id           $category_id     \
+			  -level        $level]
+    }
+    
+    # Sort the data
+    tree::sorter::sort -multirow categories_tree
+    

+ + @author helsleya@cs.ucr.edu + + @param multirow The name of the multirow data-source to make sort-keys for. + @param sort_by The name of the column that will be populated with the + sort-key. + + @see tree::sorter::make_full_key_for + @see tree::sorter::sort +} { + # Get access to state-variables + upvar ${multirow}_tree_sorter_sort_stack stack + upvar ${multirow}_tree_sorter_sort_column saved_sort_by + + # Initialize state-variables + set stack [list] + set saved_sort_by $sort_by +} + +ad_proc -public workflow::tree::sorter::make_full_key_for { + {-multirow:required} + {-partial_key:required} + {-id:required} + {-level:required} +} { + Make a key that can be stored with the given row in a + connect by-ordered tree. This updates some state variables + and makes a key from their values. Once all rows are processed, a call to + tree::sorter::sort should be made to sort the multirow data. + + @author helsleya@cs.ucr.edu + + @param multirow The name of the multirow data-source to make a sort-key for. + @param partial_key The primary field by which the tree will be (hierarchically) sorted. + @param id The ID to be used as a tie-breaker when sorting. This should be a candidate-key. + @param level The depth of the node uniquely identified by id + @return A key for sorting rows in a tree + + @see tree::sorter::create + @see tree::sorter::sort +} { + # Get access to state-variables + upvar ${multirow}_tree_sorter_sort_stack stack + + set top [llength $stack] + set newtop [expr $level - 1] + + # Pop from the top of the stack and the sort-key if necessary + if {$newtop < $top && $newtop >= 0} { + set stack [lreplace $stack $newtop end] + } + + # Push current node onto stack and sort-key + lappend stack "$partial_key $id" + set full_sort_key "//[join $stack //]" + + return $full_sort_key +} + +ad_proc -public workflow::tree::sorter::sort { + {-multirow:required} +} { + Sort the given multirow using the full-sort-keys built earlier by the calls + to tree::sorter proc + tree::sorter::make_full_key_for created with + tree::sorter::create. + + @author helsleya@cs.ucr.edu + + @param multirow The name of the multirow data-source to be sorted. + + @see tree::sorter::create + @see tree::sorter::make_full_key_for +} { + # Get access to state-variables + upvar ${multirow}_tree_sorter_sort_column sort_by + upvar ${multirow}_tree_sorter_sorted_rows rows + + # Get access to the data + set rows [uplevel "template::util::multirow_to_list $multirow"] + + if {[llength $rows] > 1} { + # Find the correct index to sort by + set header_row [lindex $rows 0] + set sort_index [expr 1 + [lsearch -exact $header_row $sort_by]] + + # Sort the data + set rows [lsort -index $sort_index $rows] + + # Convert the data back into a multirow data-source + uplevel "template::util::list_to_multirow $multirow \$${multirow}_tree_sorter_sorted_rows" + } +} + +ad_proc -public workflow::tree::sorter::sort_list_of_lists { + {-list:required} + {-sort_by:required} + {-object_id:required} + {-parent_object_id:required} +} { +

Sort the given list-of-lists. The list should have 3-4 columns: +

+

+ The display name is the only column which is not critical for sorting the + tree. + +

Example:

+

+    # Get a list that can be used as the options of a 'select' widget in ad_form
+    set directories [db_list_of_lists get_directories {
+	select  lpad(' ', (level-1)*4*6 + 1, ' ') || directory_name as directory_name,
+	                 directory_id,
+	                 parent_directory_id,
+	                 directory_name
+	           from  directories
+	         start   with parent_directory_id    is null
+	         connect by prior directory_id       = parent_directory_id
+    }]
+     
+    # Sort the options
+    set directories [tree::sorter::sort_lists_of_lists -list $directories -sort_by 3 -object_id 1 -parent_object_id 2]
+    

+ + @author helsleya@cs.ucr.edu + + @param list The list-of-lists that should be sorted as a tree. + @param sort_by The index of the element in each row which should + be used to sort by. + @param object_id The index of the element in each row which uniquely + identifies it. + @param parent_object_id The index of the element in each row which contains + the unique identifier of its parent. +} { + # Setup the format for sort-keys + set max_length_of_sort_by 0 + foreach row $list { + set length_of_sort_by [string length [lindex $row $sort_by]] + if {$length_of_sort_by > $max_length_of_sort_by} { + set max_length_of_sort_by $length_of_sort_by + } + } + set sort_key_format "%-${max_length_of_sort_by}s%08s" + + # Setup the list to save results in and two stacks + set tree [list] + set sort_key_stack [list] + set object_id_stack [list] + + # Make a full-sort-key for each row + foreach row $list { + set oid [lindex $row $object_id] + set parent_id [lindex $row $parent_object_id] + set sort_key [format $sort_key_format [lindex $row $sort_by] $parent_id] + + # Find parent in stack + set pos_in_stack [expr [lsearch $object_id_stack $parent_id] + 1] + + # Clear anything after parent from the stack + if {$pos_in_stack < [llength $object_id_stack]} { + set object_id_stack [lreplace $object_id_stack \ + $pos_in_stack \ + end ] + set sort_key_stack [lreplace $sort_key_stack \ + $pos_in_stack \ + end ] + } + + # Push the current object on the stack + lappend object_id_stack $oid + lappend sort_key_stack $sort_key + + # Save the full_sort_key with the row in the tree + set full_sort_key [join $sort_key_stack //] + lappend row $full_sort_key + lappend tree $row + } + + # Sort the rows + set tree [lsort -dictionary -index end $tree] + + return $tree +}