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.
+ # 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: +
+ # 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
+}