def get_bottom_center_element(self): rect_svg_group, rect_svg_width, rect_svg_height = a_cross_in_a_rectangle( width=self.theme['bottom-center-rectangle']['width'], height=self.theme['bottom-center-rectangle']['height'], rect_spec=self.theme['bottom-center-rectangle'], cross_spec=self.theme['bottom-center-inner-shape']) rect_svg_element = SvgElement(svg=rect_svg_group, width=rect_svg_width, height=rect_svg_height) tilde_svg_group, tilde_svg_width, tilde_svg_height = a_tilde_in_a_rectangular_shape( width=self.theme['bottom-center-rectangle']['width'], height=self.theme['bottom-center-rectangle']['height'], rect_spec=self.theme['bottom-center-rectangle'], tilde_spec=self.theme['bottom-center-inner-shape']) tilde_svg_element = SvgElement(svg=tilde_svg_group, width=tilde_svg_width, height=tilde_svg_height) svg_group, group_width, group_height = align_and_combine_horizontally( [rect_svg_element, tilde_svg_element]) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): svg_group, group_width, group_height = a_cross_inside_a_circular_shape( radius=self.theme['inner-circle']['radius'], inner_shape_spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def assemble_labels(self): if 'hide_label' in self.lane_data['styles'] and self.lane_data['styles']['hide_label'] == 'true': return None group_id = '{0}:{1}-label'.format(self.bpmn_id, self.lane_id) # get the lane label, its min_width and max_width is the pool collection's height + all label_group, group_width, group_height = text_inside_a_rectangle( text=self.lane_data['label'], min_width=self.svg_element.height, max_width=self.svg_element.height, rect_spec=self.theme['rectangle'], text_spec=self.theme['text'], debug_enabled=False) label_group.set_id(id=group_id) # now we need to add the pool-collection's label just right to it child_label_element = self.pool_collection_instance.assemble_labels() transformer = TransformBuilder() child_label_xy = Point(group_width, 0) transformer.setTranslation(child_label_xy) child_label_element.svg.set_transform(transformer.getTransform()) label_group.addElement(child_label_element.svg) group_width = group_width + child_label_element.width # wrap it in a svg element self.label_element = SvgElement(svg=label_group, width=group_width, height=group_height) # pprint(self.label_element.svg.getXML()) return self.label_element
def assemble_elements(self): info('assembling lanes for [{0}] DONE'.format(self.bpmn_id)) # wrap it in a svg group group_id = '{0}-lanes'.format(self.bpmn_id) svg_group = G(id=group_id) # height of the lane collection is sum of height of all lanes with gaps between lanes max_lane_width = self.theme['pad-spec']['left'] current_y = self.theme['pad-spec']['top'] transformer = TransformBuilder() for child_lane_class in self.child_lane_classes: swim_lane_element = child_lane_class.assemble_elements() current_x = self.theme['pad-spec']['left'] + float(child_lane_class.lane_data['styles'].get('move_x', 0)) swim_lane_element.xy = Point(current_x, current_y) transformer.setTranslation(swim_lane_element.xy) swim_lane_element.svg.set_transform(transformer.getTransform()) svg_group.addElement(swim_lane_element.svg) max_lane_width = max(max_lane_width, current_x + swim_lane_element.width) current_y = current_y + swim_lane_element.height + self.theme['dy-between-lanes'] group_width = self.theme['pad-spec']['left'] + max_lane_width + self.theme['pad-spec']['right'] group_height = current_y - self.theme['dy-between-lanes'] + self.theme['pad-spec']['bottom'] # add the ractangle lane_collection_rect_svg = Rect(width=group_width, height=group_height) lane_collection_rect_svg.set_style(StyleBuilder(self.theme['style']).getStyle()) svg_group.addElement(lane_collection_rect_svg) # wrap it in a svg element self.svg_element = SvgElement(svg=svg_group, width=group_width, height=group_height) self.lane_collection.element = self.svg_element info('assembling lanes for [{0}] DONE'.format(self.bpmn_id)) return self.svg_element
def assemble_labels(self): group_id = '{0}:{1}-pools-labels'.format(self.bpmn_id, self.lane_id) svg_group = G(id=group_id) group_width = 0 transformer = TransformBuilder() for child_pool_class in self.child_pool_classes: child_label_element = child_pool_class.assemble_labels() if child_label_element is None: continue # the y position of this pool label in the group will be its corresponding swim-pool's y position child_label_xy = Point(0, child_pool_class.svg_element.xy.y) transformer.setTranslation(child_label_xy) child_label_element.svg.set_transform(transformer.getTransform()) svg_group.addElement(child_label_element.svg) group_width = max(child_label_element.width, group_width) group_height = self.svg_element.height # wrap it in a svg element self.label_element = SvgElement(svg=svg_group, width=group_width, height=group_height) return self.label_element
def get_top_left_element(self): radius = self.theme['top-left-inner-shape']['height'] / 2 svg_group, group_width, group_height = two_gears_inside_a_circular_shape( radius=radius, inner_shape_spec=self.theme['top-left-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def to_svg(self): info('......processing node [{0}:{1}:{2}:{3}]'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) # the label element label_group, label_group_width, label_group_height = text_inside_a_rectangle( text=self.node_data['label'], min_width=self.theme['rectangle']['min-width'], max_width=self.theme['rectangle']['max-width'], rect_spec=self.theme['rectangle'], text_spec=self.theme['text']) # the data store svg data_store_group, data_store_group_width, data_store_group_height = include_and_scale_svg( spec=self.theme['shape-spec']) # wrap them in a svg group ----------------------------------------------------------------- svg_group = G(id=self.group_id) # the folded rectangle is below an empty space of the same height of the label d_x = max((label_group_width - data_store_group_width) / 2, 0) data_store_group_xy = '{0},{1}'.format( (label_group_width - data_store_group_width) / 2, label_group_height + self.snap_point_offset) transformer = TransformBuilder() transformer.setTranslation(data_store_group_xy) data_store_group.set_transform(transformer.getTransform()) # the label is vertically below the folded rectangle label_group_xy = '{0},{1}'.format( 0, label_group_height + self.snap_point_offset + data_store_group_height) transformer = TransformBuilder() transformer.setTranslation(label_group_xy) label_group.set_transform(transformer.getTransform()) # place the elements svg_group.addElement(data_store_group) svg_group.addElement(label_group) # extend the height so that a blank space of the same height as text is at the bottom so that the circle's left edge is at dead vertical center group_width = label_group_width group_height = label_group_height + self.snap_point_offset + data_store_group_height + self.snap_point_offset + label_group_height # snap points snap_points = self.snap_points(group_width, group_height) self.snap_offset_x = (label_group_width - data_store_group_width ) / 2 + self.snap_point_offset self.snap_offset_y = label_group_height + self.snap_point_offset * 2 # self.draw_snaps(snap_points, svg_group, x_offset=self.snap_offset_x, y_offset=self.snap_offset_y) info('......processing node [{0}:{1}:{2}:{3}] DONE'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) self.svg_element = SvgElement(svg=svg_group, width=group_width, height=group_height, snap_points=snap_points, label_pos='bottom') return self.svg_element
def get_inside_element(self): radius = min(self.theme['diamond']['diagonal-x'], self.theme['diamond']['diagonal-y']) * 0.25 svg_group, group_width, group_height = a_circle( radius=radius, spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_top_left_element(self): svg_group, group_width, group_height = an_envelop( width=self.theme['top-left-inner-shape']['width'], height=self.theme['top-left-inner-shape']['height'], spec=self.theme['top-left-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): svg_group, group_width, group_height = a_star( width=self.theme['diamond']['diagonal-x'], height=self.theme['diamond']['diagonal-y'], spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): radius = radius_of_the_circle_inside_the_diamond( width=self.theme['diamond']['diagonal-x'], height=self.theme['diamond']['diagonal-y']) svg_group, group_width, group_height = an_x_inside_a_circular_shape( radius=radius, inner_shape_spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): radius = min(self.theme['diamond']['diagonal-x'], self.theme['diamond']['diagonal-y']) * 0.27 svg_group, group_width, group_height = an_equilateral_pentagon_in_a_circle( radius=radius, circle_spec=self.theme['inner-circle'], pentagon_spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_top_left_element(self): width = self.theme['top-left-inner-shape']['width'] height = self.theme['top-left-inner-shape']['height'] svg_group, group_width, group_height = a_right_arrow( width=width, height=height, spec=self.theme['top-left-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_bottom_center_element(self): width = self.theme['bottom-center-inner-shape']['width'] height = self.theme['bottom-center-inner-shape']['height'] svg_group, group_width, group_height = three_bars( width=width, height=height, spec=self.theme['bottom-center-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_bottom_center_element(self): svg_group, group_width, group_height = a_cross_in_a_rectangle( width=self.theme['bottom-center-rectangle']['width'], height=self.theme['bottom-center-rectangle']['height'], rect_spec=self.theme['bottom-center-rectangle'], cross_spec=self.theme['bottom-center-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): radius = radius_of_the_circle_inside_the_diamond( self.theme['diamond']['diagonal-x'], self.theme['diamond']['diagonal-y']) - 4 svg_group, group_width, group_height = a_cross_in_a_circle( radius=radius, circle_spec=self.theme['inner-circle'], cross_spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_top_left_element(self): inner_svg, inner_width, inner_height = an_equilateral_pentagon_inside_a_circular_shape( radius=self.theme['top-left-circle']['radius'], inner_shape_spec=self.theme['top-left-inner-shape']) svg_group, group_width, group_height = envelop_and_center_in_a_circle( circle_spec=self.theme['top-left-circle'], svg=inner_svg, svg_width=inner_width, svg_height=inner_height) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def get_inside_element(self): pad = 3 outer_radius = min(self.theme['diamond']['diagonal-x'], self.theme['diamond']['diagonal-y']) * 0.27 inner_radius = outer_radius - pad svg_group, group_width, group_height = an_equilateral_pentagon_in_two_concentric_circles( outer_radius=outer_radius, inner_radius=inner_radius, outer_circle_spec=self.theme['inner-circle'], inner_circle_spec=self.theme['inner-circle'], pad=pad, pentagon_spec=self.theme['inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def assemble_elements(self): # channels are vertically stacked # root channels start at x=0 # channels that are branch of some parent channel start horizontally after the node to which the first node is the to-node # TODO: how to horizontally shift channels based on to-node's relationship with from-node from other lane/pool # TODO: how and where to place the island channel? # wrap it in a svg group group_id = '{0}:{1}:{2}'.format(self.bpmn_id, self.lane_id, self.pool_id) svg_group = G(id=group_id) # lay the channels, channels are vertically stacked, but only the root channels start at left, branches ar horizontally positioned so that they fall to the right of their parent node's position group_width = 0 current_y = self.theme['pad-spec']['top'] transformer = TransformBuilder() for channel_list in self.channel_collection.channel_lists: for channel in channel_list: # if it is a root channel it, starts at left (0) x position if channel.is_root == True: current_x = self.theme['pad-spec']['left'] else: # we find the parent node from which this channel is branched and position accordingly if channel.parent_channel is not None: parent_channel_object = self.channel_collection.channel_by_name( channel.parent_channel) x_pos = parent_channel_object.element.xy.x + parent_channel_object.x_of_node( node_id=channel.name ) + self.theme['dx-between-elements'] else: x_pos = 0 if x_pos != 0: current_x = self.theme['pad-spec'][ 'left'] + x_pos + self.theme['dx-between-elements'] else: current_x = self.theme['pad-spec']['left'] # TODO: the channel may be moved up to just below of a previous channel if there is no part of any in between channels in the middle channel_element = channel.element channel_element_svg = channel_element.svg channel_element.xy = Point(current_x + channel_element.move_x, current_y) transformer.setTranslation(channel_element.xy) channel_element_svg.set_transform(transformer.getTransform()) svg_group.addElement(channel_element_svg) group_width = max( group_width, channel_element.xy.x + channel_element.width + self.theme['pad-spec']['right']) current_y = current_y + channel_element.height + self.theme[ 'dy-between-channels'] group_height = current_y - self.theme[ 'dy-between-channels'] + self.theme['pad-spec']['bottom'] # add the ractangle channel_collection_rect_svg = Rect(width=group_width, height=group_height) channel_collection_rect_svg.set_style( StyleBuilder(self.theme['style']).getStyle()) svg_group.addElement(channel_collection_rect_svg) # wrap it in a svg element self.svg_element = SvgElement(svg=svg_group, width=group_width, height=group_height) # store the svg and dimensions for future reference self.channel_collection.element = self.svg_element return self.svg_element
def get_top_left_element(self): svg_group, group_width, group_height = include_and_scale_svg( spec=self.theme['top-left-inner-shape']) return SvgElement(svg=svg_group, width=group_width, height=group_height)
def create_flow(self, from_node, to_node, label, label_style): # first we need the diection from from_node to to_node if from_node.element.xy.west_of(to_node.element.xy): direction = 'east' # are they adjacent or apart if (self.channel.node_ordinal(to_node) - self.channel.node_ordinal(from_node)) == 1: distance = 'adjacent' flow_route = None else: distance = 'apart' flow_route = self.channel.get_a_channel_flow_route(boundary=direction, node=from_node, peer=to_node) elif from_node.element.xy.east_of(to_node.element.xy): direction = 'west' distance = '*' flow_route = self.channel.get_a_channel_flow_route(boundary=direction, node=from_node, peer=to_node) else: warn('from-node [{0}] and to-node [{1}] starts at same x position, they can not be connected inside a channel which is supposed to have all nodes on different x position on same y') return None # now we know the rule to chose for snapping and routing for the *from* and *to* node from_node_spec = self.snap_rules[direction][distance]['from-node'][from_node.category] to_node_spec = self.snap_rules[direction][distance]['to-node'][to_node.category] from_node_points_in_channel = self.channel.points_to_channel_flow_area( boundary=from_node_spec['cross-through-boundary'], node=from_node, side=from_node_spec['side'], position=from_node_spec['position'], role='from', approach_snap_point_from=from_node_spec['approach-snap-point-from'], peer=to_node, edge_type=self.edge_type, flow_route=flow_route) if from_node_points_in_channel is None: warn('could not calculate snap points for from-node [{0}]'.format(from_node.id)) return None if from_node_spec['cross-through-boundary']: # self.mark_points([from_node_points_in_channel[-1]], self.channel.element.svg, 'red') pass to_node_points_in_channel = self.channel.points_to_channel_flow_area( boundary=to_node_spec['cross-through-boundary'], node=to_node, side=to_node_spec['side'], position=to_node_spec['position'], role='to', approach_snap_point_from=to_node_spec['approach-snap-point-from'], peer=to_node, edge_type=self.edge_type, flow_route=flow_route) if to_node_points_in_channel is None: warn('could not calculate snap points for to-node [{0}]'.format(to_node.id)) return None if to_node_spec['cross-through-boundary']: # self.mark_points([to_node_points_in_channel[0]], self.channel.element.svg, 'green') pass # we now have two segments we connect the last point of *from-segment* to the first point of *to-segment* through a north-ward path flow_points = from_node_points_in_channel + to_node_points_in_channel flow_points = optimize_points(flow_points) # determine the placement of the label label_data = None if label is not None and label != '': label_data = {} label_data['text'] = label # get the longest horizontal line segment point_from, point_to = longest_horizontal_line_segment(flow_points) # the main connecting line for ChannelFlow is a horizontal line label_data['line-points'] = {'from': point_from, 'to': point_to} label_data['line-direction'] = 'east-west' # the text should be placed on top of the line label_data['placement'] = label_style.get('placement', 'north') label_data['move-x'] = float(label_style.get('move_x', 0)) label_data['move-y'] = float(label_style.get('move_y', 0)) # we have the points, now create and return the flow flow_svg, flow_width, flow_height = a_flow(flow_points, label_data, self.theme, self.flow_scope) # debug('[{0}] -> [{1}] : {2}'.format(from_node.id, to_node.id, flow_points)) return SvgElement(svg=flow_svg, width=flow_width, height=flow_height)
def create_flow(self, from_node, to_node, label, label_style): from_node_lane_number, from_node_lane_id, from_node_pool_number, from_node_pool_id, _ = self.lane_collection.lane_and_pool_number_and_id(from_node) to_node_lane_number, to_node_lane_id, to_node_pool_number, to_node_pool_id, _ = self.lane_collection.lane_and_pool_number_and_id(to_node) if self.validate(from_node_lane_number, to_node_lane_number, from_node, to_node) == False: return None # debug('LANE [{0}] - [{1}:{2}:{3}] --> [{4}:{5}:{6}]'.format(self.lane_collection.lane_id, from_node_lane_number, from_node_lane_id, from_node.id, to_node_lane_number, to_node_lane_id, to_node.id)) # first we need the diection from from_node to to_node if from_node_lane_number < to_node_lane_number: direction = 'south' from_node_lane_boundary = 'south' to_node_lane_boundary = 'north' from_node_pool_boundary = 'south' to_node_pool_boundary = 'north' else: direction = 'north' from_node_lane_boundary = 'north' to_node_lane_boundary = 'south' from_node_pool_boundary = 'north' to_node_pool_boundary = 'south' # node-positions from_node_channel, from_node_ordinal = self.lane_collection.channel_and_ordinal(from_node) if from_node_ordinal == len(from_node_channel.nodes) - 1: from_node_position = 'east-most' else: from_node_position = '*' to_node_channel, to_node_ordinal = self.lane_collection.channel_and_ordinal(to_node) if to_node_ordinal == 0: to_node_position = 'west-most' else: to_node_position = '*' # now we know the rule to chose for snapping and routing for the *from* and *to* node from_node_spec = self.snap_rules[direction]['from-node'][from_node_position][from_node.category] to_node_spec = self.snap_rules[direction]['to-node'][to_node_position][to_node.category] from_node_lane_boundary = from_node_spec['channel_boundary'] to_node_lane_boundary = to_node_spec['channel_boundary'] from_node_points_in_bpmn_coordinate = self.lane_collection.outside_the_lane( lane_boundary=from_node_lane_boundary, lane_number=from_node_lane_number, pool_boundary=from_node_pool_boundary, pool_number=from_node_pool_number, channel_boundary=from_node_spec['channel_boundary'], channel=from_node_channel, node=from_node, side=from_node_spec['side'], position=from_node_spec['position'], role='from', approach_snap_point_from=from_node_spec['approach-snap-point-from'], peer=to_node, edge_type=self.edge_type) if from_node_points_in_bpmn_coordinate is None: warn('could not calculate snap points for from-node [{0}]'.format(from_node.id)) return None to_node_points_in_bpmn_coordinate = self.lane_collection.outside_the_lane( lane_boundary=to_node_lane_boundary, lane_number=to_node_lane_number, pool_boundary=to_node_pool_boundary, pool_number=to_node_pool_number, channel_boundary=to_node_spec['channel_boundary'], channel=to_node_channel, node=to_node, side=to_node_spec['side'], position=to_node_spec['position'], role='to', approach_snap_point_from=to_node_spec['approach-snap-point-from'], peer=to_node, edge_type=self.edge_type) if to_node_points_in_bpmn_coordinate is None: warn('could not calculate snap points for to-node [{0}]'.format(to_node.id)) return None # we always connect from the north point to the south point if from_node_points_in_bpmn_coordinate[-1].north_of(to_node_points_in_bpmn_coordinate[0]): north_point = from_node_points_in_bpmn_coordinate[-1] south_point = to_node_points_in_bpmn_coordinate[0] joining_points = self.lane_collection.connect_southward(from_lane_number=from_node_lane_number, point_from=north_point, to_lane_number=to_node_lane_number, point_to=south_point) else: north_point = to_node_points_in_bpmn_coordinate[0] south_point = from_node_points_in_bpmn_coordinate[-1] joining_points = self.lane_collection.connect_southward(from_lane_number=to_node_lane_number, point_from=north_point, to_lane_number=from_node_lane_number, point_to=south_point) joining_points.reverse() # self.mark_points(joining_points, self.lane_collection.element.svg, color='red') # we have the points, now create and return the flow flow_points = from_node_points_in_bpmn_coordinate + joining_points + to_node_points_in_bpmn_coordinate # determine the placement of the label label_data = None if label is not None and label != '': label_data = {} label_data['text'] = label # get the first vertical line segment having a min-length point_from, point_to = first_vertical_line_segment_longer_than(flow_points, 30) # the main connecting line for ChannelFlow is a horizontal line label_data['line-points'] = {'from': point_from, 'to': point_to} label_data['line-direction'] = 'north-south' # the text should be placed on top of the line label_data['placement'] = label_style.get('placement', 'east') label_data['move-x'] = float(label_style.get('move_x', 0)) label_data['move-y'] = float(label_style.get('move_y', 20)) flow_svg, flow_width, flow_height = a_flow(flow_points, label_data, self.theme, self.flow_scope) return SvgElement(svg=flow_svg, width=flow_width, height=flow_height)
def to_svg(self): info('......processing node [{0}:{1}:{2}:{3}]'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) # the rectangle element if self.node_data['label'] == '': label = id_to_label(self.node_id) else: label = self.node_data['label'] rectangle_group, rectangle_group_width, rectangle_group_height = text_inside_a_rectangle( text=label, min_width=self.theme['rectangle']['min-width'], max_width=self.theme['rectangle']['max-width'], rect_spec=self.theme['rectangle'], text_spec=self.theme['text']) # get the inside bottom center element bottom_center_element = self.get_bottom_center_element() # if an inside bottom center element is to be placed, the element should have a gap from the rectangle bottom if bottom_center_element is not None: bottom_center_group, bottom_center_group_width, bottom_center_group_height = bottom_center_element.svg, bottom_center_element.width, bottom_center_element.height bottom_center_group_xy = '{0},{1}'.format( (rectangle_group_width - bottom_center_group_width) / 2, rectangle_group_height - bottom_center_group_height - self.theme['rectangle']['inner-shape-margin-spec']['bottom']) transformer = TransformBuilder() transformer.setTranslation(bottom_center_group_xy) bottom_center_group.set_transform(transformer.getTransform()) rectangle_group.addElement(bottom_center_group) # get the inside top left element top_left_element = self.get_top_left_element() # if an inside bottom center element is to be placed, the element should have a gap from the rectangle bottom if top_left_element is not None: top_left_group, top_left_group_width, top_left_group_height = top_left_element.svg, top_left_element.width, top_left_element.height top_left_group_xy = '{0},{1}'.format( self.theme['rectangle']['inner-shape-margin-spec']['left'], self.theme['rectangle']['inner-shape-margin-spec']['top']) transformer = TransformBuilder() transformer.setTranslation(top_left_group_xy) top_left_group.set_transform(transformer.getTransform()) rectangle_group.addElement(top_left_group) # if there is an outer rectangle process that if 'outer-rectangle' in self.theme: rectangle_group, rectangle_group_width, rectangle_group_height = envelop_and_center_in_a_rectangle( svg=rectangle_group, svg_width=rectangle_group_width, svg_height=rectangle_group_height, rect_spec=self.theme['outer-rectangle']) # snap points snap_points = self.snap_points(rectangle_group_width, rectangle_group_height) self.snap_offset_x = self.snap_point_offset self.snap_offset_y = self.snap_point_offset # self.draw_snaps(snap_points, rectangle_group, x_offset=self.snap_offset_x, y_offset=self.snap_offset_y) label_pos = 'middle' info('......processing node [{0}:{1}:{2}:{3}] DONE'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) self.svg_element = SvgElement(svg=rectangle_group, width=rectangle_group_width, height=rectangle_group_height, snap_points=snap_points, label_pos=label_pos) return self.svg_element
def to_svg(self): info('......processing node [{0}:{1}:{2}:{3}]'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) # the label element label_group, label_group_width, label_group_height = text_inside_a_rectangle( text=self.node_data['label'], min_width=self.theme['rectangle']['min-width'], max_width=self.theme['rectangle']['max-width'], rect_spec=self.theme['rectangle'], text_spec=self.theme['text']) # the folded rectangle folded_rectangle_group, folded_rectangle_group_width, folded_rectangle_group_height = a_folded_rectangle( width=self.theme['folded-rectangle']['width'], height=self.theme['folded-rectangle']['height'], spec=self.theme['folded-rectangle']) # top left element inside ------------------------------------------------------- top_left_element = self.get_top_left_element() # position properly inside the folder rectangle at top left if top_left_element is not None: top_left_group, top_left_group_width, top_left_group_height = top_left_element.svg, top_left_element.width, top_left_element.height top_left_group_xy = Point( self.theme['folded-rectangle']['pad-spec']['left'], self.theme['folded-rectangle']['pad-spec']['top']) transformer = TransformBuilder() transformer.setTranslation(top_left_group_xy) top_left_group.set_transform(transformer.getTransform()) folded_rectangle_group.addElement(top_left_group) # bottom center element inside ------------------------------------------------------- bottom_center_element = self.get_bottom_center_element() # position properly inside the folder rectangle at bottom center if bottom_center_element is not None: bottom_center_group, bottom_center_group_width, bottom_center_group_height = bottom_center_element.svg, bottom_center_element.width, bottom_center_element.height bottom_center_group_xy = '{0},{1}'.format( (folded_rectangle_group_width - bottom_center_group_width) / 2, folded_rectangle_group_height - bottom_center_group_height - self.theme['folded-rectangle']['pad-spec']['bottom']) transformer = TransformBuilder() transformer.setTranslation(bottom_center_group_xy) bottom_center_group.set_transform(transformer.getTransform()) folded_rectangle_group.addElement(bottom_center_group) # wrap them in a svg group ----------------------------------------------------------------- svg_group = G(id=self.group_id) # the folded rectangle is below an empty space of the same height of the label folded_rectangle_group_xy = Point( (label_group_width - folded_rectangle_group_width) / 2, label_group_height + self.snap_point_offset) transformer = TransformBuilder() transformer.setTranslation(folded_rectangle_group_xy) folded_rectangle_group.set_transform(transformer.getTransform()) # where the label will be positioned depends on the value of label_pos if self.label_pos == 'bottom': label_group_xy = Point( 0, label_group_height + self.snap_point_offset + folded_rectangle_group_height + self.snap_point_offset) transformer = TransformBuilder() transformer.setTranslation(label_group_xy) label_group.set_transform(transformer.getTransform()) # place the elements svg_group.addElement(folded_rectangle_group) svg_group.addElement(label_group) # extend the height so that a blank space of the same height as text is at the bottom so that the circle's left edge is at dead vertical center group_width = label_group_width group_height = label_group_height + self.snap_point_offset + folded_rectangle_group_height + self.snap_point_offset + label_group_height # snap points snap_points = self.snap_points(group_width, group_height) self.snap_offset_x = (label_group_width - folded_rectangle_group_width ) / 2 + self.snap_point_offset self.snap_offset_y = label_group_height + self.snap_point_offset * 2 # self.draw_snaps(snap_points, svg_group, x_offset=self.snap_offset_x, y_offset=self.snap_offset_y) info('......processing node [{0}:{1}:{2}:{3}] DONE'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) self.svg_element = SvgElement(svg=svg_group, width=group_width, height=group_height, snap_points=snap_points, label_pos=self.label_pos) return self.svg_element
def to_svg(self): info('......processing node [{0}:{1}:{2}:{3}]'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) # the label element label_group, label_group_width, label_group_height = text_inside_a_rectangle( text=self.node_data['label'], min_width=self.theme['rectangle']['min-width'], max_width=self.theme['rectangle']['max-width'], rect_spec=self.theme['rectangle'], text_spec=self.theme['text']) # the circle element if 'inner-circle' in self.theme: circle_group, circle_group_width, circle_group_height = two_concentric_circles( outer_radius=self.theme['circle']['radius'], inner_radius=self.theme['inner-circle']['radius'], outer_circle_spec=self.theme['circle'], inner_circle_spec=self.theme['inner-circle']) else: circle_group, circle_group_width, circle_group_height = a_circle( radius=self.theme['circle']['radius'], spec=self.theme['circle']) # get the inside element inside_element = self.get_inside_element() # if an inside element is to be placed inside the circle group, place it so that the inside object's center and the circle's center is same if inside_element is not None: inside_group, inside_group_width, inside_group_height = inside_element.svg, inside_element.width, inside_element.height inside_group_xy = Point( (circle_group_width - inside_group_width) / 2, (circle_group_height - inside_group_height) / 2) transformer = TransformBuilder() transformer.setTranslation(inside_group_xy) inside_group.set_transform(transformer.getTransform()) circle_group.addElement(inside_group) # wrap it in a svg group svg_group = G(id=self.group_id) # the circle is vertically below the label, keep a gap of *snap_point_offset* circle_group_xy = Point((label_group_width - circle_group_width) / 2, label_group_height + self.snap_point_offset) transformer = TransformBuilder() transformer.setTranslation(circle_group_xy) circle_group.set_transform(transformer.getTransform()) # where the label will be positioned depends on the value of label_pos if self.label_pos == 'bottom': label_group_xy = Point( 0, label_group_height + self.snap_point_offset + circle_group_height + self.snap_point_offset) transformer = TransformBuilder() transformer.setTranslation(label_group_xy) label_group.set_transform(transformer.getTransform()) # place the label and circle svg_group.addElement(label_group) svg_group.addElement(circle_group) # extend the height so that a blank space of the same height as text is at the bottom so that the circle's left edge is at dead vertical center group_width = label_group_width group_height = label_group_height + self.snap_point_offset + circle_group_height + self.snap_point_offset + label_group_height # snap points snap_points = self.snap_points(group_width, group_height) self.snap_offset_x = (label_group_width - circle_group_width) / 2 + self.snap_point_offset self.snap_offset_y = label_group_height + self.snap_point_offset * 2 # self.draw_snaps(snap_points, svg_group, x_offset=self.snap_offset_x, y_offset=self.snap_offset_y) info('......processing node [{0}:{1}:{2}:{3}] DONE'.format( self.bpmn_id, self.lane_id, self.pool_id, self.node_id)) self.svg_element = SvgElement(svg=svg_group, width=group_width, height=group_height, snap_points=snap_points, label_pos=self.label_pos) return self.svg_element
def create_flow(self, from_node, to_node, label, label_style): from_node_channel, from_node_ordinal = self.channel_collection.channel_and_ordinal( from_node) to_node_channel, to_node_ordinal = self.channel_collection.channel_and_ordinal( to_node) if self.validate(from_node_channel, to_node_channel, from_node, to_node) == False: return None # first we need the diection from from_node to to_node if from_node_channel.number < to_node_channel.number: direction = 'south' else: direction = 'north' # node-positions if from_node_ordinal == len(from_node_channel.nodes) - 1: from_node_position = 'east-most' else: from_node_position = '*' if to_node_ordinal == 0: to_node_position = 'west-most' else: to_node_position = '*' # now we know the rule to chose for snapping and routing for the *from* and *to* node from_node_spec = self.snap_rules[direction]['from-node'][ from_node_position][from_node.category] to_node_spec = self.snap_rules[direction]['to-node'][to_node_position][ to_node.category] from_node_points_in_pool_coordinate = self.channel_collection.points_to_pool_flow_area( boundary=from_node_spec['cross-through-boundary'], channel=from_node_channel, node=from_node, side=from_node_spec['side'], position=from_node_spec['position'], role='from', approach_snap_point_from=from_node_spec[ 'approach-snap-point-from'], peer=to_node, edge_type=self.edge_type) if from_node_points_in_pool_coordinate is None: warn('could not calculate snap points for from-node [{0}]'.format( from_node.id)) return None to_node_points_in_pool_coordinate = self.channel_collection.points_to_pool_flow_area( boundary=to_node_spec['cross-through-boundary'], channel=to_node_channel, node=to_node, side=to_node_spec['side'], position=to_node_spec['position'], role='to', approach_snap_point_from=to_node_spec['approach-snap-point-from'], peer=from_node, edge_type=self.edge_type) if to_node_points_in_pool_coordinate is None: warn('could not calculate snap points for to-node [{0}]'.format( to_node.id)) return None # we always connect from the north point to the south point # if the points are vertically close it means that they are inside the same pool-flow-area between same two channels, we just connect them by adjusting the to_point y positions from_node_end_point = from_node_points_in_pool_coordinate[-1] to_node_start_point = to_node_points_in_pool_coordinate[0] # TODO: this 48 is a hack, it shold be the sy-between-channels value if abs(from_node_end_point.y - to_node_start_point.y) <= 24: joining_points = [ Point(to_node_start_point.x, from_node_end_point.y) ] else: # TODO: the connect_southward is problematic, no if from_node_points_in_pool_coordinate[-1].north_of( to_node_points_in_pool_coordinate[0]): north_point = from_node_points_in_pool_coordinate[-1] south_point = to_node_points_in_pool_coordinate[0] joining_points = self.channel_collection.connect_southward( point_from=north_point, point_to=south_point) else: # self.mark_points([from_node_points_in_pool_coordinate[-1]], self.channel_collection.element.svg, 'red') # self.mark_points([to_node_points_in_pool_coordinate[0]], self.channel_collection.element.svg, 'green') north_point = to_node_points_in_pool_coordinate[0] south_point = from_node_points_in_pool_coordinate[-1] joining_points = self.channel_collection.connect_southward( point_from=north_point, point_to=south_point) joining_points.reverse() # self.mark_points(joining_points, self.channel_collection.element.svg, 'blue') # self.mark_points(from_node_points_in_pool_coordinate, self.channel_collection.element.svg, 'red') # self.mark_points(to_node_points_in_pool_coordinate, self.channel_collection.element.svg, 'green') # we have the points, now create and return the flow flow_points = from_node_points_in_pool_coordinate + joining_points + to_node_points_in_pool_coordinate # debug('[{0}] -> [{1}] point outside channel from node: {2}'.format(from_node.id, to_node.id, from_node_points_in_pool_coordinate[-1])) # debug('[{0}] -> [{1}] point outside channel to node: {2}'.format(from_node.id, to_node.id, to_node_points_in_pool_coordinate[0])) # debug('[{0}] -> [{1}] joining points : {2}'.format(from_node.id, to_node.id, joining_points)) flow_points = optimize_points(flow_points) # determine the placement of the label label_data = None if label is not None and label != '': label_data = {} label_data['text'] = label # get the first vertical line segment having a min-length point_from, point_to = first_vertical_line_segment_longer_than( flow_points, 30) # the main connecting line for ChannelFlow is a horizontal line label_data['line-points'] = {'from': point_from, 'to': point_to} label_data['line-direction'] = 'north-south' # the text should be placed on top of the line label_data['placement'] = label_style.get('placement', 'east') label_data['move-x'] = float(label_style.get('move_x', 0)) label_data['move-y'] = float(label_style.get('move_y', 20)) flow_svg, flow_width, flow_height = a_flow(flow_points, label_data, self.theme, self.flow_scope) # debug('[{0}] -> [{1}] : {2}'.format(from_node.id, to_node.id, flow_points)) return SvgElement(svg=flow_svg, width=flow_width, height=flow_height)