I make a lot of diagrams for papers, and I found myself annotating shapes, then as the diagram changes I have to move the anotations around and it was difficult to get them correctly placed.
To solve the problem I created two scripts. The first Annotate creates an annotation for each selected object. It prompts for the position, and the margins.
The Reposition Annotation script only works on Annotations created with the annotation script. It allows the annotation to be repositioned with a different margin or on a different side of the object. Note you can select multiple objects, select no change, and then simply adjust the margins.
I have script versions and applications created with Applescript studio, that have a much better interface. Scripts first
Code:
-- Annotate OmniGraffle objects by Dan Thomsen
-- customizable parameters
set label_font to "Arial Narrow"
set label_font_size to 12
set annotate_color to "Steel"
set annotate_thickness to 0.5
set layer_name to "Annotation Layer"
set annotate_label to "annotate here"
-- parameters that the user specifies
global horizontal_margin
global vertical_margin
global horizontal_position
global vertical_position
global annotation_position
set horizontal_margin to 20 -- default value
set vertical_margin to 20 -- default value
-- valid values are left, center right
set horizontal_position to "left" -- default value
-- valid values are top, middle, bottom
set vertical_position to "top" -- default value
-- constants
-- these are used to create magnets on the two ends and the middle of the edge
set left_edge to {{-0.5, -0.5}, {-0.5, 0}, {-0.5, 0.5}}
set right_edge to {{0.5, -0.5}, {0.5, 0}, {0.5, 0.5}}
set bottom_edge to {{-0.5, 0.5}, {0, 0.5}, {0.5, 0.5}}
set top_edge to {{-0.5, -0.5}, {0, -0.5}, {0.5, -0.5}}
-- variables
set annotation_size to {0, 0} -- used to store the size of the annotated text
set text_alignment to left -- default value
try
tell front window of application "OmniGraffle Professional"
activate
-- all annotations are put on their own layer
-- if the annotation layer does not exist create it
if (name of layers) does not contain layer_name then
make new layer at beginning of layers with properties {name:layer_name, visible:true, prints:true}
end if
-- get the annotation layer
repeat with alayer in layers
if name of alayer is equal to layer_name then
set annotation_layer to alayer
exit repeat
end if
end repeat
set objects to selection
if (length of objects < 1) then
error "no objects selected"
end if
if not my prompt_user() then
error "user abort"
end if
-- the position is saved in the annotation so that the margins can latter be adjusted by running the Reposition Annotation script
set annotation_tag to {annotation:true, position:annotation_position}
set object_count to 0
-- we are going to draw an annotation at 0,0 just to get its actual size
make new shape at beginning of graphics of annotation_layer with properties {origin:{0, 0}, draws stroke:no, fill:no fill, text:{size:label_font_size, color:annotate_color, font:label_font, alignment:text_alignment, text:(annotate_label)}, draws shadow:false, autosizing:full}
set annotation_size to size of graphic 1
-- now that we have the size we don't need the object any more
delete graphic 1
repeat with obj in objects
set object_count to object_count + 1
set obj_size to size of obj
set obj_orig to origin of obj
if horizontal_position is "left" then
set x_pos to (item 1 of obj_orig) - horizontal_margin - (item 1 of annotation_size)
set text_alignment to right
set magnet_list to right_edge
set x_header_start to x_pos + (item 1 of annotation_size)
else if horizontal_position = "center" then
set x_pos to (item 1 of obj_orig) + ((item 1 of obj_size) - (item 1 of annotation_size)) / 2
set text_alignment to center
if vertical_position = "top" then
set magnet_list to bottom_edge
else if vertical_position = "middle" then
display alert "Invalid Placement." message "Cannot place an annotation in the center of the object." buttons {"OK"} default button 1
exit repeat -- the script
else -- vertical_position ="bottom"
set magnet_list to top_edge
end if
set x_header_start to x_pos
else
set x_pos to (item 1 of obj_orig) + (item 1 of obj_size) + horizontal_margin
set text_alignment to left
set magnet_list to left_edge
set x_header_start to x_pos
end if
if vertical_position = "top" then
set y_pos to (item 2 of obj_orig) - vertical_margin - (item 2 of annotation_size)
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
else if vertical_position = "middle" then
set y_pos to (item 2 of obj_orig) + ((item 2 of obj_size) - (item 2 of annotation_size)) / 2
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
if horizontal_position = "left" then
set magnet_list to right_edge
else if horizontal_position = "center" then
display alert "Invalid Placement." message "Cannot place an annotation in the center of the object." buttons {"OK"} default button 1
exit repeat -- the script
else -- horizontal_position = right
set magnet_list to left_edge
end if
else -- bottom
set y_pos to (item 2 of obj_orig) + (item 2 of obj_size) + vertical_margin
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
end if
make new shape at beginning of graphics of annotation_layer with properties {origin:{x_pos, y_pos}, size:annotation_size, draws stroke:no, fill:no fill, magnets:magnet_list, user data:annotation_tag, text:{size:label_font_size, color:annotate_color, font:label_font, alignment:text_alignment, text:(annotate_label)}, draws shadow:false, autosizing:full}
set annotated_text to graphic 1
set origin of annotated_text to {x_pos, y_pos}
-- I have not been able to get connect the line directly between two objects
-- I have to use the point list and then define source and destination after
make new line at beginning of graphics of annotation_layer with properties {stroke color:annotate_color, thickness:annotate_thickness, point list:{x_pos, y_pos}}
set connector_line to graphic 1
set source of connector_line to obj
set destination of connector_line to annotated_text
set head magnet of connector_line to 2
make new line at beginning of graphics of annotation_layer with properties {stroke color:annotate_color, thickness:annotate_thickness, point list:{{x_header_start, y_header_start}, {x_header_start, y_header_end}}}
set header_line to graphic 1
set source of header_line to annotated_text
set tail magnet of header_line to 1
set destination of header_line to annotated_text
set head magnet of header_line to 3
end repeat
-- Need 'as list' if there's only one item. Pesky; took ages to figure out.
-- object_count is multiplied by three because the text, header line and connector line are each object
set selection to a reference to graphics 1 thru (object_count * 3) as list
end tell
on error error_message
tell front window of application "OmniGraffle Professional"
activate
--say error_message
if not error_message is equal to "user abort" then
display alert "No selection." message "This script adds an annotation to an object. Select some, then run it again." buttons {"OK"} default button 1
end if
end tell
end try
-- Ask the user to fill in the four global variables
on prompt_user()
tell front window of application "OmniGraffle Professional"
-- Prompt the user as to where to place annotation
try
set answer to choose from list {"NW", "N", "NE", "W", "E", "SW", "S", "SE"} with prompt "Placement:"
if answer is false then return false
set annotation_position to (item 1 of answer)
set horizontal_position to my get_horizontal_position(annotation_position)
set vertical_position to my get_vertical_position(annotation_position)
on error error_message
display alert error_message
return false
end try
try
set answer to display dialog "Enter horizontal margin" default answer "20"
if answer is false then return false
set horizontal_margin to text returned of answer as number
set answer to display dialog "Enter vertical margin" default answer "20"
if answer is false then return false
set vertical_margin to text returned of answer as number
on error error_message
if error_message is "OmniGraffle Professional got an error: User canceled." then return false
display alert error_message buttons {"OK"} default button 1
return false
end try
return true
end tell
end prompt_user
on get_horizontal_position(position)
if {"NW", "W", "SW"} contains position then
return ("left")
else if {"N", "S"} contains position then
return "center"
else if {"NE", "E", "SE"} contains position then
return "right"
else
return ("left")
end if
end get_horizontal_position
on get_vertical_position(position)
if {"NW", "N", "NE"} contains position then
return ("top")
else if {"W", "E"} contains position then
return "middle"
else if {"SW", "S", "SE"} contains position then
return "bottom"
else
return ("top")
end if
end get_vertical_position