class Roby::GUI::PlanDotLayout
This class uses Graphviz (i.e. the “dot” tool) to compute a layout for a given plan
Constants
- DOT_TO_QT_SCALE_FACTOR_X
- DOT_TO_QT_SCALE_FACTOR_Y
- FLOAT_VALUE
Attributes
bounding_rects[R]
display[R]
dot_input[R]
object_pos[R]
plan[R]
Public Class Methods
parse_dot_layout(dot_layout, options = Hash.new)
click to toggle source
# File lib/roby/gui/plan_dot_layout.rb, line 256 def self.parse_dot_layout(dot_layout, options = Hash.new) options = Kernel.validate_options options, scale_x: DOT_TO_QT_SCALE_FACTOR_X, scale_y: DOT_TO_QT_SCALE_FACTOR_Y scale_x = options[:scale_x] scale_y = options[:scale_y] current_graph_id = nil bounding_rects = Hash.new object_pos = Hash.new full_line = "" dot_layout.each do |line| line.chomp! full_line << line.strip if line[-1] == ?\\ or line[-1] == ?, full_line.chomp! next end case full_line when /(\w+).*\[.*pos="(#{FLOAT_VALUE}),(#{FLOAT_VALUE})"/ object_pos[$1] = Qt::PointF.new(Float($2) * scale_x, Float($3) * scale_y) when /subgraph cluster_(\w+)/ current_graph_id = $1 when /bb="(#{FLOAT_VALUE}),(#{FLOAT_VALUE}),(#{FLOAT_VALUE}),(#{FLOAT_VALUE})"/ bb = [$1, $2, $3, $4].map { |c| Float(c) } bb[0] *= scale_x bb[2] *= scale_x bb[1] *= scale_x bb[3] *= scale_x bounding_rects[current_graph_id] = Qt::RectF.new(bb[0], bb[1], bb[2] - bb[0], bb[3] - bb[1]) end full_line = "" end graph_bb = bounding_rects.delete(nil) if !graph_bb raise "Graphviz failed to generate a layout for this plan" end bounding_rects.each_value do |bb| bb.x -= graph_bb.x bb.y = graph_bb.y - bb.y - bb.height end object_pos.each do |id, pos| pos.x -= graph_bb.x pos.y = graph_bb.y - pos.y end return bounding_rects, object_pos end
Public Instance Methods
<<(string)
click to toggle source
Add a string to the resulting Dot input file
# File lib/roby/gui/plan_dot_layout.rb, line 250 def <<(string); dot_input << string end
apply()
click to toggle source
# File lib/roby/gui/plan_dot_layout.rb, line 422 def apply plan.apply_layout(bounding_rects, object_pos, display) end
layout(display, plan, options = Hash.new)
click to toggle source
Generates a layout internal for each task, allowing to place the events according to the propagations
# File lib/roby/gui/plan_dot_layout.rb, line 337 def layout(display, plan, options = Hash.new) @display = display options = Kernel.validate_options options, scale_x: DOT_TO_QT_SCALE_FACTOR_X, scale_y: DOT_TO_QT_SCALE_FACTOR_Y # We first layout only the tasks separately. This allows to find # how to layout the events within the task, and know the overall # task sizes all_tasks = Set.new bounding_boxes, positions = run_dot(graph_type: 'graph', layout_method: 'fdp', scale_x: 1.0 / 100, scale_y: 1.0 / 100) do display.plans.each do |p| p_tasks = p.tasks | p.finalized_tasks p_tasks.each do |task| task.to_dot_events(display, self) end all_tasks.merge(p_tasks) p.propagated_events.each do |_, sources, to, _| sources.each do |from| if from.respond_to?(:task) && to.respond_to?(:task) && from.task == to.task from_id, to_id = from.dot_id, to.dot_id if from_id && to_id self << " #{from.dot_id} -- #{to.dot_id}\n" end end end end end end # Ignore graphviz-generated BBs, recompute from the event # positions and then make their positions relative event_positions = Hash.new all_tasks.each do |t| next if !display.displayed?(t) bb = Qt::RectF.new if p = positions[t.dot_id] bb |= Qt::RectF.new(p, p) end t.each_event do |ev| next if !display.displayed?(ev) p = positions[ev.dot_id] bb |= Qt::RectF.new(p, p) end t.each_event do |ev| next if !display.displayed?(ev) event_positions[ev.dot_id] = positions[ev.dot_id] - bb.topLeft end graphics = display.graphics[t] graphics.rect = Qt::RectF.new(0, 0, bb.width, bb.height) end @bounding_rects, @object_pos = run_dot(scale_x: 1.0 / 50, scale_y: 1.0 / 15) do # Finally, generate the whole plan plan.to_dot(display, self, 0) # Take the signalling into account for the layout. At this stage, # task events are represented by their tasks display.plans.each do |p| p.propagated_events.each do |_, sources, to, _| to_id = if to.respond_to?(:task) then to.task.dot_id else to.dot_id end sources.each do |from| from_id = if from.respond_to?(:task) from.task.dot_id else from.dot_id end if from_id && to_id self << " #{from.dot_id} -> #{to.dot_id}\n" end end end end end object_pos.merge!(event_positions) @plan = plan end
run_dot(options = Hash.new) { |dot_input| ... }
click to toggle source
# File lib/roby/gui/plan_dot_layout.rb, line 307 def run_dot(options = Hash.new) options, parsing_options = Kernel.filter_options options, graph_type: 'digraph', layout_method: display.layout_method @@index ||= 0 @@index += 1 # Dot input file @dot_input = Tempfile.new("roby_dot") # Dot output file dot_output = Tempfile.new("roby_layout") dot_input << "#{options[:graph_type]} relations {\n" yield(dot_input) dot_input << "}\n" dot_input.flush # Make sure the GUI keeps being updated while dot is processing FileUtils.cp dot_input.path, "/tmp/dot-input-#{@@index}.dot" system("#{options[:layout_method]} #{dot_input.path} > #{dot_output.path}") FileUtils.cp dot_output.path, "/tmp/dot-output-#{@@index}.dot" # Load only task bounding boxes from dot, update arrows later lines = File.open(dot_output.path) { |io| io.readlines } PlanDotLayout.parse_dot_layout(lines, parsing_options) end