Module: Trailblazer::Diagram::BPMN
- Defined in:
- lib/trailblazer/diagram/bpmn.rb
Overview
rubocop:disable Metrics/ModuleLength
Defined Under Namespace
Modules: Representer Classes: Bounds, Definitions, Edge, Plane, Shape, Waypoint
Class Method Summary collapse
- .fromBottom(bounds) ⇒ Object
- .fromRight(left) ⇒ Object
- .fromTop(bounds) ⇒ Object
-
.Path(source, target, do_straight_line) ⇒ Object
rubocop:disable Metrics/AbcSize.
-
.to_xml(activity, linear_task_ids = nil) ⇒ Object
FIXME: this should be called “linear layouter or something” Render an ‘Activity`’s circuit to a BPMN 2.0 XML ‘<process>` structure.
- .toLeft(bounds) ⇒ Object
-
.topological_sort(model) ⇒ Object
Helps sorting the tasks in a process “topologically”, which is basically what the Sequence does for us, but this works for any kind of process.
Class Method Details
.fromBottom(bounds) ⇒ Object
142 143 144 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 142 def self.fromBottom(bounds) [bounds.x + bounds.width / 2, bounds.y + bounds.height] end |
.fromRight(left) ⇒ Object
134 135 136 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 134 def self.fromRight(left) [left.x + left.width, left.y + left.height / 2] end |
.fromTop(bounds) ⇒ Object
146 147 148 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 146 def self.fromTop(bounds) [bounds.x + bounds.width / 2, bounds.y] end |
.Path(source, target, do_straight_line) ⇒ Object
rubocop:disable Metrics/AbcSize
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 118 def self.Path(source, target, do_straight_line) # rubocop:disable Metrics/AbcSize if source.y == target.y # ---> [Waypoint.new(*fromRight(source)), Waypoint.new(*toLeft(target))] elsif do_straight_line [Waypoint.new(*fromBottom(source)), Waypoint.new(*toLeft(target))] elsif target.y > source.y # target below source. [ l = Waypoint.new(*fromBottom(source)), r = Waypoint.new(l.x, target.y + target.height / 2), Waypoint.new(target.x, r.y) ] else # target above source. [l = Waypoint.new(*fromTop(source)), r = Waypoint.new(l.x, target.y + target.height / 2), Waypoint.new(target.x, r.y)] end end |
.to_xml(activity, linear_task_ids = nil) ⇒ Object
FIXME: this should be called “linear layouter or something” Render an ‘Activity`’s circuit to a BPMN 2.0 XML ‘<process>` structure.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 37 def self.to_xml(activity, linear_task_ids = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength model = Trailblazer::Developer::Activity::Graph.to_model(activity.graph) linear_task_ids ||= topological_sort(model) # this layouter doesn't want End events in the linear part, we arrange them manually. linear_task_ids -= model.end_events.map(&:id) linear_task_ids -= model.start_events.map(&:id) linear_tasks = linear_task_ids.collect do |id| model.task.find { |task| task.id == id } || raise("task #{id} is not in model!") end start_x = 200 y_right = 200 y_left = 300 event_width = 54 shape_width = 81 shape_height = 54 shape_to_shape = 45 current = start_x shapes = [] # add start. shapes << Shape.new( "Shape_#{model.start_events[0][:id]}", model.start_events[0][:id], Bounds.new(current, y_right, event_width, event_width) ) current += event_width + shape_to_shape # add tasks. linear_tasks.each do |task| is_right = %i[pass step].include?(task.[:created_by]) shapes << Shape.new( "Shape_#{task[:id]}", task[:id], Bounds.new(current, is_right ? y_right : y_left, shape_width, shape_height) ) current += shape_width + shape_to_shape end # add ends. horizontal_end_offset = 90 defaults = { "End.success" => {y: y_right}, "End.failure" => {y: y_left}, "End.pass_fast" => {y: y_right - 90}, "End.fail_fast" => {y: y_left + 90} } success_end_events = [] failure_end_events = [] # rubocop:disable Lint/UselessAssignment model.end_events.each do |evt| id = evt[:id] y = defaults[id] ? defaults[id][:y] : success_end_events.last + horizontal_end_offset success_end_events << y shapes << Shape.new("Shape_#{id}", id, Bounds.new(current, y, event_width, event_width)) end edges = [] model.sequence_flow.each do |flow| source = shapes.find { |shape| shape.id == "Shape_#{flow.sourceRef}" }.bounds target = shapes.find { |shape| shape.id == "Shape_#{flow.targetRef}" }.bounds edges << Edge.new("SequenceFlow_#{flow[:id]}", flow[:id], Path(source, target, target.x != current)) end diagram = Struct.new(:plane).new(Plane.new(model.id, shapes, edges)) # render XML. Representer::Definitions.new(Definitions.new(model, diagram)).to_xml end |
.toLeft(bounds) ⇒ Object
138 139 140 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 138 def self.toLeft(bounds) [bounds.x, bounds.y + bounds.height / 2] end |
.topological_sort(model) ⇒ Object
Helps sorting the tasks in a process “topologically”, which is basically what the Sequence does for us, but this works for any kind of process. DISCUSS: should we work on the Model or Graph interface?
19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/trailblazer/diagram/bpmn.rb', line 19 def self.topological_sort(model) edges = {} model.end_events.each { |task| edges[task.id] = {} } model.sequence_flow.each do |edge| edges[edge.sourceRef] ||= [] edges[edge.sourceRef] << edge.targetRef end # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} each_node = ->(&b) { edges.each_key(&b) } each_child = ->(n, &b) { edges[n].each(&b) } TSort.tsort(each_node, each_child).reverse #=> [4, 2, 3, 1] end |