コード例 #1
0
ファイル: pygraphviz.py プロジェクト: kfly89/pm4py-core
def apply(bpmn_graph, parameters=None):
    """
    Layouts a BPMN graph (inserting the positions of the nodes and the layouting of the edges)

    Parameters
    -------------
    bpmn_graph
        BPMN graph
    parameters
        Parameters of the algorithm

    Returns
    -------------
    bpmn_graph
        BPMN graph with layout information
    """
    if parameters is None:
        parameters = {}

    try:
        import pygraphviz
    except ImportError:
        raise ImportError('missing pygraphviz: ',
                          'http://pygraphviz.github.io/')

    nodes = bpmn_graph.get_nodes()
    flows = bpmn_graph.get_flows()
    graph = bpmn_graph.get_graph()

    # convert nx graph to pygraphviz graph
    A = pygraphviz.AGraph(name=bpmn_graph.get_name(), strict=True, directed=True, rankdir="LR")

    graph_nodes, graph_edges = get_sorted_nodes_edges(bpmn_graph)

    for n in graph_nodes:
        A.add_node(n)

    for tup in graph_edges:
        A.add_edge(tup[0], tup[1])

    # use built-in layout function from pygraphviz
    A.layout(prog="dot")

    focus = exec_utils.get_param_value(Parameters.FOCUS, parameters, 0.42)
    scaling_fact_x = exec_utils.get_param_value(Parameters.SCALING_FACT_X, parameters, 320.0) * focus
    scaling_fact_y = exec_utils.get_param_value(Parameters.SCALING_FACT_Y, parameters, 150.0) * focus
    task_wh = exec_utils.get_param_value(Parameters.TASK_WH, parameters, 60)

    # add node positions to BPMN nodes
    for n in graph_nodes:
        node = pygraphviz.Node(A, n)
        xs = node.attr["pos"].split(',')
        node_pos = tuple(float(x) for x in xs)
        # width = round(100.0 * float(node.attr["width"]) / 7.0)
        n.set_x(int(node_pos[0]))
        n.set_y(int(node_pos[1]))
        n.set_height(task_wh)
        if isinstance(n, BPMN.Task):
            this_width = min(round(2 * task_wh), round(2 * (len(n.get_name()) + 7) * task_wh / 22.0))
            n.set_width(this_width)
        else:
            n.set_width(task_wh)

    # stretch BPMN a bit, cause y coordinates are ususally pretty small compared to x
    max_node_x = max(node.get_x() for node in nodes)
    max_node_y = max(node.get_y() for node in nodes)
    different_x = len(set(node.get_x() for node in nodes))
    different_y = len(set(node.get_y() for node in nodes))

    stretch_fact_x = scaling_fact_x * different_x / max_node_x
    stretch_fact_y = scaling_fact_y * different_y / max_node_y

    for node in nodes:
        node.set_x(round(node.get_x() * stretch_fact_x))
        node.set_y(round(node.get_y() * stretch_fact_y))

    outgoing_edges = dict()
    ingoing_edges = dict()
    sources_dict = dict()
    targets_dict = dict()

    for flow in flows:
        source = flow.get_source()
        target = flow.get_target()

        x_src = source.get_x()
        x_trg = target.get_x()
        y_src = source.get_y()
        y_trg = target.get_y()

        sources_dict[(x_src, y_src)] = source
        targets_dict[(x_trg, y_trg)] = target

        diff_x = abs(x_trg - x_src)
        diff_y = abs(y_src - y_trg)

        if not (x_src, y_src) in outgoing_edges:
            outgoing_edges[(x_src, y_src)] = {}
        outgoing_edges[(x_src, y_src)][(x_trg, y_trg)] = {EndpointDirection.RIGHT: 0.0, EndpointDirection.LEFT: 0.0,
                                                          EndpointDirection.TOP: 0.0, EndpointDirection.BOTTOM: 0.0}
        if not (x_trg, y_trg) in ingoing_edges:
            ingoing_edges[(x_trg, y_trg)] = {}
        ingoing_edges[(x_trg, y_trg)][(x_src, y_src)] = {EndpointDirection.RIGHT: 0.0, EndpointDirection.LEFT: 0.0,
                                                         EndpointDirection.TOP: 0.0, EndpointDirection.BOTTOM: 0.0}

        if x_trg > x_src:
            outgoing_edges[(x_src, y_src)][(x_trg, y_trg)][EndpointDirection.RIGHT] = diff_x / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(x_src, y_src)][EndpointDirection.LEFT] = diff_x / (diff_x + diff_y)
        else:
            outgoing_edges[(x_src, y_src)][(x_trg, y_trg)][EndpointDirection.LEFT] = diff_x / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(x_src, y_src)][EndpointDirection.RIGHT] = diff_x / (diff_x + diff_y)

        if y_src > y_trg:
            outgoing_edges[(x_src, y_src)][(x_trg, y_trg)][EndpointDirection.TOP] = diff_y / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(x_src, y_src)][EndpointDirection.BOTTOM] = diff_y / (diff_x + diff_y)
        else:
            outgoing_edges[(x_src, y_src)][(x_trg, y_trg)][EndpointDirection.BOTTOM] = diff_y / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(x_src, y_src)][EndpointDirection.TOP] = diff_y / (diff_x + diff_y)

    # normalization
    outgoing_edges0 = deepcopy(outgoing_edges)
    ingoing_edges0 = deepcopy(ingoing_edges)

    for p1 in outgoing_edges:
        sum_right = 0.0
        sum_left = 0.0
        sum_top = 0.0
        sum_bottom = 0.0
        for p2 in outgoing_edges[p1]:
            sum_right += outgoing_edges0[p1][p2][EndpointDirection.RIGHT]
            sum_left += outgoing_edges0[p1][p2][EndpointDirection.LEFT]
            sum_top += outgoing_edges0[p1][p2][EndpointDirection.TOP]
            sum_bottom += outgoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        if p1 in ingoing_edges:
            for p2 in ingoing_edges[p1]:
                sum_right += ingoing_edges0[p1][p2][EndpointDirection.RIGHT]
                sum_left += ingoing_edges0[p1][p2][EndpointDirection.LEFT]
                sum_top += ingoing_edges0[p1][p2][EndpointDirection.TOP]
                sum_bottom += ingoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        for p2 in outgoing_edges[p1]:
            if sum_right > 0:
                outgoing_edges[p1][p2][EndpointDirection.RIGHT] = outgoing_edges[p1][p2][
                                                                      EndpointDirection.RIGHT] ** 2 / sum_right
            if sum_left > 0:
                outgoing_edges[p1][p2][EndpointDirection.LEFT] = outgoing_edges[p1][p2][
                                                                     EndpointDirection.LEFT] ** 2 / sum_left
            if sum_top > 0:
                outgoing_edges[p1][p2][EndpointDirection.TOP] = outgoing_edges[p1][p2][
                                                                    EndpointDirection.TOP] ** 2 / sum_top
            if sum_bottom > 0:
                outgoing_edges[p1][p2][EndpointDirection.BOTTOM] = outgoing_edges[p1][p2][
                                                                       EndpointDirection.BOTTOM] ** 2 / sum_bottom
    for p1 in ingoing_edges:
        sum_right = 0.0
        sum_left = 0.0
        sum_top = 0.0
        sum_bottom = 0.0
        for p2 in ingoing_edges[p1]:
            sum_right += ingoing_edges0[p1][p2][EndpointDirection.RIGHT]
            sum_left += ingoing_edges0[p1][p2][EndpointDirection.LEFT]
            sum_top += ingoing_edges0[p1][p2][EndpointDirection.TOP]
            sum_bottom += ingoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        if p1 in outgoing_edges:
            for p2 in outgoing_edges[p1]:
                sum_right += outgoing_edges0[p1][p2][EndpointDirection.RIGHT]
                sum_left += outgoing_edges0[p1][p2][EndpointDirection.LEFT]
                sum_top += outgoing_edges0[p1][p2][EndpointDirection.TOP]
                sum_bottom += outgoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        for p2 in ingoing_edges[p1]:
            if sum_right > 0:
                ingoing_edges[p1][p2][EndpointDirection.RIGHT] = ingoing_edges[p1][p2][
                                                                     EndpointDirection.RIGHT] ** 2 / sum_right
            if sum_left > 0:
                ingoing_edges[p1][p2][EndpointDirection.LEFT] = ingoing_edges[p1][p2][
                                                                    EndpointDirection.LEFT] ** 2 / sum_left
            if sum_top > 0:
                ingoing_edges[p1][p2][EndpointDirection.TOP] = ingoing_edges[p1][p2][
                                                                   EndpointDirection.TOP] ** 2 / sum_top
            if sum_bottom > 0:
                ingoing_edges[p1][p2][EndpointDirection.BOTTOM] = ingoing_edges[p1][p2][
                                                                      EndpointDirection.BOTTOM] ** 2 / sum_bottom

    # keep best direction
    for p1 in outgoing_edges:
        for p2 in outgoing_edges[p1]:
            vals = sorted([(x, y) for x, y in outgoing_edges[p1][p2].items()], key=lambda x: x[1], reverse=True)
            outgoing_edges[p1][p2] = vals[0][0]
    for p1 in ingoing_edges:
        for p2 in ingoing_edges[p1]:
            vals = sorted([(x, y) for x, y in ingoing_edges[p1][p2].items()], key=lambda x: x[1], reverse=True)
            ingoing_edges[p1][p2] = vals[0][0]

    total_counter = dict()
    partial_counter = dict()
    for p1 in outgoing_edges:
        if p1 not in total_counter:
            total_counter[p1] = Counter()
        for p2 in outgoing_edges[p1]:
            dir = outgoing_edges[p1][p2]
            total_counter[p1][dir] += 1
    for p1 in ingoing_edges:
        if p1 not in total_counter:
            total_counter[p1] = Counter()
        for p2 in ingoing_edges[p1]:
            dir = ingoing_edges[p1][p2]
            total_counter[p1][dir] += 1

    outgoing_edges_dirs = deepcopy(outgoing_edges)
    ingoing_edges_dirs = deepcopy(ingoing_edges)

    # decide exiting/entering point for edges
    for p1 in outgoing_edges:
        node = sources_dict[p1]
        if p1 not in partial_counter:
            partial_counter[p1] = Counter()
        sorted_outgoing_edges = sorted(outgoing_edges[p1], key=lambda x: x, reverse=False)
        for p2 in sorted_outgoing_edges:
            dir = outgoing_edges[p1][p2]
            partial_counter[p1][dir] += 1
            if dir == EndpointDirection.RIGHT:
                outgoing_edges[p1][p2] = get_right_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.LEFT:
                outgoing_edges[p1][p2] = get_left_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.TOP:
                outgoing_edges[p1][p2] = get_top_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.BOTTOM:
                outgoing_edges[p1][p2] = get_bottom_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
    for p1 in ingoing_edges:
        node = targets_dict[p1]
        if p1 not in partial_counter:
            partial_counter[p1] = Counter()
        sorted_ingoing_edges = sorted(ingoing_edges[p1], key=lambda x: x, reverse=False)
        for p2 in sorted_ingoing_edges:
            dir = ingoing_edges[p1][p2]
            partial_counter[p1][dir] += 1
            if dir == EndpointDirection.RIGHT:
                ingoing_edges[p1][p2] = get_right_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.LEFT:
                ingoing_edges[p1][p2] = get_left_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.TOP:
                ingoing_edges[p1][p2] = get_top_edge_coord(node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.BOTTOM:
                ingoing_edges[p1][p2] = get_bottom_edge_coord(node, p1, partial_counter[p1], total_counter[p1])

    # order the left-entering ingoing edges better
    for p1 in ingoing_edges:
        vals = [(x, y) for x, y in ingoing_edges[p1].items() if y[0] == p1[0]]
        if len(vals) > 1:
            vals_x = [x[0] for x in vals]
            vals_y = [x[1] for x in vals]
            vals_x = sorted(vals_x)
            vals_y = sorted(vals_y)
            for i in range(len(vals_x)):
                ingoing_edges[p1][vals_x[i]] = vals_y[i]

    # set waypoints for edges
    for flow in flows:
        source = flow.get_source()
        target = flow.get_target()

        flow.del_waypoints()

        x_src = source.get_x()
        x_trg = target.get_x()
        y_src = source.get_y()
        y_trg = target.get_y()
        p1 = (x_src, y_src)
        p2 = (x_trg, y_trg)

        source_x = outgoing_edges[p1][p2][0]
        source_y = outgoing_edges[p1][p2][1]
        target_x = ingoing_edges[p2][p1][0]
        target_y = ingoing_edges[p2][p1][1]
        dir_source = outgoing_edges_dirs[p1][p2]
        dir_target = ingoing_edges_dirs[p2][p1]

        middle_x = (source_x + target_x) / 2.0
        middle_y = (source_y + target_y) / 2.0

        flow.add_waypoint((source_x, source_y))
        if dir_source in [EndpointDirection.LEFT, EndpointDirection.RIGHT]:
            if dir_target in [EndpointDirection.LEFT, EndpointDirection.RIGHT]:
                flow.add_waypoint((middle_x, source_y))
                flow.add_waypoint((middle_x, target_y))
            elif dir_target in [EndpointDirection.TOP, EndpointDirection.BOTTOM]:
                flow.add_waypoint((target_x, source_y))
        elif dir_source in [EndpointDirection.TOP, EndpointDirection.BOTTOM]:
            if dir_target in [EndpointDirection.TOP, EndpointDirection.BOTTOM]:
                flow.add_waypoint((source_x, middle_y))
                flow.add_waypoint((target_x, middle_y))
            elif dir_target in [EndpointDirection.LEFT, EndpointDirection.RIGHT]:
                flow.add_waypoint((source_x, target_y))

        flow.add_waypoint((target_x, target_y))

    return bpmn_graph
コード例 #2
0
ファイル: classic.py プロジェクト: pm4py/pm4py-core
def apply(bpmn_graph: BPMN,
          parameters: Optional[Dict[Any, Any]] = None) -> graphviz.Digraph:
    """
    Visualize a BPMN graph

    Parameters
    -------------
    bpmn_graph
        BPMN graph
    parameters
        Parameters of the visualization, including:
         - Parameters.FORMAT: the format of the visualization
         - Parameters.RANKDIR: the direction of the representation (default: LR)

    Returns
    ------------
    gviz
        Graphviz representation
    """
    if parameters is None:
        parameters = {}

    from pm4py.objects.bpmn.obj import BPMN
    from pm4py.objects.bpmn.util.sorting import get_sorted_nodes_edges

    image_format = exec_utils.get_param_value(Parameters.FORMAT, parameters,
                                              "png")
    rankdir = exec_utils.get_param_value(Parameters.RANKDIR, parameters, "LR")
    font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters,
                                           12)
    font_size = str(font_size)
    bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters,
                                         "transparent")

    filename = tempfile.NamedTemporaryFile(suffix='.gv')
    viz = Digraph("",
                  filename=filename.name,
                  engine='dot',
                  graph_attr={'bgcolor': bgcolor})
    viz.graph_attr['rankdir'] = rankdir

    nodes, edges = get_sorted_nodes_edges(bpmn_graph)

    for n in nodes:
        n_id = str(id(n))
        if isinstance(n, BPMN.Task):
            viz.node(n_id, shape="box", label=n.get_name(), fontsize=font_size)
        elif isinstance(n, BPMN.StartEvent):
            viz.node(n_id,
                     label="",
                     shape="circle",
                     style="filled",
                     fillcolor="green",
                     fontsize=font_size)
        elif isinstance(n, BPMN.EndEvent):
            viz.node(n_id,
                     label="",
                     shape="circle",
                     style="filled",
                     fillcolor="orange",
                     fontsize=font_size)
        elif isinstance(n, BPMN.ParallelGateway):
            viz.node(n_id, label="+", shape="diamond", fontsize=font_size)
        elif isinstance(n, BPMN.ExclusiveGateway):
            viz.node(n_id, label="X", shape="diamond", fontsize=font_size)
        elif isinstance(n, BPMN.InclusiveGateway):
            viz.node(n_id, label="O", shape="diamond", fontsize=font_size)
        else:
            viz.node(n_id, label="", shape="circle", fontsize=font_size)

    for e in edges:
        n_id_1 = str(id(e[0]))
        n_id_2 = str(id(e[1]))

        viz.edge(n_id_1, n_id_2)

    viz.attr(overlap='false')

    viz.format = image_format

    return viz
コード例 #3
0
ファイル: graphviz.py プロジェクト: pm4py/pm4py-core
def apply(bpmn_graph, parameters=None):
    """
    Layouts a BPMN graph (inserting the positions of the nodes and the layouting of the edges)

    Parameters
    -------------
    bpmn_graph
        BPMN graph
    parameters
        Parameters of the algorithm

    Returns
    -------------
    bpmn_graph
        BPMN graph with layout information
    """
    from graphviz import Digraph
    from pm4py.visualization.common import save as gsave

    if parameters is None:
        parameters = {}

    nodes = bpmn_graph.get_nodes()
    flows = bpmn_graph.get_flows()

    filename_gv = tempfile.NamedTemporaryFile(suffix='.gv')
    filename_gv.close()
    filename_svg = tempfile.NamedTemporaryFile(suffix='.svg')
    filename_svg.close()
    viz = Digraph(bpmn_graph.get_name(),
                  filename=filename_gv.name,
                  engine='dot')
    viz.format = "svg"
    viz.graph_attr['rankdir'] = 'LR'

    graph_nodes, graph_edges = get_sorted_nodes_edges(bpmn_graph)

    nodes_dict = {}
    inv_nodes_dict = {}
    for n in graph_nodes:
        node_uuid = str(uuid.uuid4()).replace("-", "")
        nodes_dict[n] = node_uuid
        inv_nodes_dict[node_uuid] = n
        viz.node(node_uuid, label=" ", shape="box")

    for tup in graph_edges:
        viz.edge(nodes_dict[tup[0]], nodes_dict[tup[1]])

    gsave.save(viz, filename_svg.name)

    nodes_pos = {}

    content = open(filename_svg.name, "r").read()
    viz_nodes = content.split("class=\"node\">")[1:]
    for node in viz_nodes:
        this_id = node.split("<title>")[1].split("</title>")[0]
        points = node.split("points=\"")[1].split("\"")[0]
        nodes_pos[inv_nodes_dict[this_id]] = points

    task_wh = exec_utils.get_param_value(Parameters.TASK_WH, parameters, 60)

    # add node positions to BPMN nodes
    for n in graph_nodes:
        node_pos = nodes_pos[n].split(" ")[0].split(",")

        pos_x = float(node_pos[0])
        pos_y = float(node_pos[1])
        n.set_x(pos_x)
        n.set_y(pos_y)
        n.set_height(task_wh)
        if isinstance(n, BPMN.Task):
            this_width = min(
                round(2 * task_wh),
                round(2 * (len(n.get_name()) + 7) * task_wh / 22.0))
            n.set_width(this_width)
        else:
            n.set_width(task_wh)

    max_x = max(1, max(abs(node.get_x()) for node in nodes))
    max_y = max(1, max(abs(node.get_y()) for node in nodes))
    different_x = len(set(node.get_x() for node in nodes))
    different_y = len(set(node.get_y() for node in nodes))

    stretch_fact_x = 1.25 * 1920.0 / max_x
    stretch_fact_y = 1080.0 / max_y

    for node in nodes:
        node.set_x(round(node.get_x() * stretch_fact_x))
        node.set_y(round(node.get_y() * stretch_fact_y))

    outgoing_edges = dict()
    ingoing_edges = dict()
    sources_dict = dict()
    targets_dict = dict()

    for flow in flows:
        source = flow.get_source()
        target = flow.get_target()

        x_src = source.get_x()
        x_trg = target.get_x()
        y_src = source.get_y()
        y_trg = target.get_y()

        sources_dict[(x_src, y_src)] = source
        targets_dict[(x_trg, y_trg)] = target

        diff_x = abs(x_trg - x_src)
        diff_y = abs(y_src - y_trg)

        if not (x_src, y_src) in outgoing_edges:
            outgoing_edges[(x_src, y_src)] = {}
        outgoing_edges[(x_src, y_src)][(x_trg, y_trg)] = {
            EndpointDirection.RIGHT: 0.0,
            EndpointDirection.LEFT: 0.0,
            EndpointDirection.TOP: 0.0,
            EndpointDirection.BOTTOM: 0.0
        }
        if not (x_trg, y_trg) in ingoing_edges:
            ingoing_edges[(x_trg, y_trg)] = {}
        ingoing_edges[(x_trg, y_trg)][(x_src, y_src)] = {
            EndpointDirection.RIGHT: 0.0,
            EndpointDirection.LEFT: 0.0,
            EndpointDirection.TOP: 0.0,
            EndpointDirection.BOTTOM: 0.0
        }

        if x_trg > x_src:
            outgoing_edges[(x_src, y_src)][(
                x_trg,
                y_trg)][EndpointDirection.RIGHT] = diff_x / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(
                x_src,
                y_src)][EndpointDirection.LEFT] = diff_x / (diff_x + diff_y)
        else:
            outgoing_edges[(x_src, y_src)][(
                x_trg,
                y_trg)][EndpointDirection.LEFT] = diff_x / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(
                x_src,
                y_src)][EndpointDirection.RIGHT] = diff_x / (diff_x + diff_y)

        if y_src > y_trg:
            outgoing_edges[(x_src, y_src)][(
                x_trg,
                y_trg)][EndpointDirection.TOP] = diff_y / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(
                x_src,
                y_src)][EndpointDirection.BOTTOM] = diff_y / (diff_x + diff_y)
        else:
            outgoing_edges[(x_src, y_src)][(
                x_trg,
                y_trg)][EndpointDirection.BOTTOM] = diff_y / (diff_x + diff_y)
            ingoing_edges[(x_trg, y_trg)][(
                x_src,
                y_src)][EndpointDirection.TOP] = diff_y / (diff_x + diff_y)

    # normalization
    outgoing_edges0 = deepcopy(outgoing_edges)
    ingoing_edges0 = deepcopy(ingoing_edges)

    for p1 in outgoing_edges:
        sum_right = 0.0
        sum_left = 0.0
        sum_top = 0.0
        sum_bottom = 0.0
        for p2 in outgoing_edges[p1]:
            sum_right += outgoing_edges0[p1][p2][EndpointDirection.RIGHT]
            sum_left += outgoing_edges0[p1][p2][EndpointDirection.LEFT]
            sum_top += outgoing_edges0[p1][p2][EndpointDirection.TOP]
            sum_bottom += outgoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        if p1 in ingoing_edges:
            for p2 in ingoing_edges[p1]:
                sum_right += ingoing_edges0[p1][p2][EndpointDirection.RIGHT]
                sum_left += ingoing_edges0[p1][p2][EndpointDirection.LEFT]
                sum_top += ingoing_edges0[p1][p2][EndpointDirection.TOP]
                sum_bottom += ingoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        for p2 in outgoing_edges[p1]:
            if sum_right > 0:
                outgoing_edges[p1][p2][
                    EndpointDirection.RIGHT] = outgoing_edges[p1][p2][
                        EndpointDirection.RIGHT]**2 / sum_right
            if sum_left > 0:
                outgoing_edges[p1][p2][
                    EndpointDirection.LEFT] = outgoing_edges[p1][p2][
                        EndpointDirection.LEFT]**2 / sum_left
            if sum_top > 0:
                outgoing_edges[p1][p2][EndpointDirection.TOP] = outgoing_edges[
                    p1][p2][EndpointDirection.TOP]**2 / sum_top
            if sum_bottom > 0:
                outgoing_edges[p1][p2][
                    EndpointDirection.BOTTOM] = outgoing_edges[p1][p2][
                        EndpointDirection.BOTTOM]**2 / sum_bottom
    for p1 in ingoing_edges:
        sum_right = 0.0
        sum_left = 0.0
        sum_top = 0.0
        sum_bottom = 0.0
        for p2 in ingoing_edges[p1]:
            sum_right += ingoing_edges0[p1][p2][EndpointDirection.RIGHT]
            sum_left += ingoing_edges0[p1][p2][EndpointDirection.LEFT]
            sum_top += ingoing_edges0[p1][p2][EndpointDirection.TOP]
            sum_bottom += ingoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        if p1 in outgoing_edges:
            for p2 in outgoing_edges[p1]:
                sum_right += outgoing_edges0[p1][p2][EndpointDirection.RIGHT]
                sum_left += outgoing_edges0[p1][p2][EndpointDirection.LEFT]
                sum_top += outgoing_edges0[p1][p2][EndpointDirection.TOP]
                sum_bottom += outgoing_edges0[p1][p2][EndpointDirection.BOTTOM]
        for p2 in ingoing_edges[p1]:
            if sum_right > 0:
                ingoing_edges[p1][p2][EndpointDirection.RIGHT] = ingoing_edges[
                    p1][p2][EndpointDirection.RIGHT]**2 / sum_right
            if sum_left > 0:
                ingoing_edges[p1][p2][EndpointDirection.LEFT] = ingoing_edges[
                    p1][p2][EndpointDirection.LEFT]**2 / sum_left
            if sum_top > 0:
                ingoing_edges[p1][p2][EndpointDirection.TOP] = ingoing_edges[
                    p1][p2][EndpointDirection.TOP]**2 / sum_top
            if sum_bottom > 0:
                ingoing_edges[p1][p2][
                    EndpointDirection.BOTTOM] = ingoing_edges[p1][p2][
                        EndpointDirection.BOTTOM]**2 / sum_bottom

    # keep best direction
    for p1 in outgoing_edges:
        for p2 in outgoing_edges[p1]:
            vals = sorted([(x, y) for x, y in outgoing_edges[p1][p2].items()],
                          key=lambda x: x[1],
                          reverse=True)
            outgoing_edges[p1][p2] = vals[0][0]
    for p1 in ingoing_edges:
        for p2 in ingoing_edges[p1]:
            vals = sorted([(x, y) for x, y in ingoing_edges[p1][p2].items()],
                          key=lambda x: x[1],
                          reverse=True)
            ingoing_edges[p1][p2] = vals[0][0]

    total_counter = dict()
    partial_counter = dict()
    for p1 in outgoing_edges:
        if p1 not in total_counter:
            total_counter[p1] = Counter()
        for p2 in outgoing_edges[p1]:
            dir = outgoing_edges[p1][p2]
            total_counter[p1][dir] += 1
    for p1 in ingoing_edges:
        if p1 not in total_counter:
            total_counter[p1] = Counter()
        for p2 in ingoing_edges[p1]:
            dir = ingoing_edges[p1][p2]
            total_counter[p1][dir] += 1

    outgoing_edges_dirs = deepcopy(outgoing_edges)
    ingoing_edges_dirs = deepcopy(ingoing_edges)

    # decide exiting/entering point for edges
    for p1 in outgoing_edges:
        node = sources_dict[p1]
        if p1 not in partial_counter:
            partial_counter[p1] = Counter()
        sorted_outgoing_edges = sorted(outgoing_edges[p1],
                                       key=lambda x: x,
                                       reverse=False)
        for p2 in sorted_outgoing_edges:
            dir = outgoing_edges[p1][p2]
            partial_counter[p1][dir] += 1
            if dir == EndpointDirection.RIGHT:
                outgoing_edges[p1][p2] = get_right_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.LEFT:
                outgoing_edges[p1][p2] = get_left_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.TOP:
                outgoing_edges[p1][p2] = get_top_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.BOTTOM:
                outgoing_edges[p1][p2] = get_bottom_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
    for p1 in ingoing_edges:
        node = targets_dict[p1]
        if p1 not in partial_counter:
            partial_counter[p1] = Counter()
        sorted_ingoing_edges = sorted(ingoing_edges[p1],
                                      key=lambda x: x,
                                      reverse=False)
        for p2 in sorted_ingoing_edges:
            dir = ingoing_edges[p1][p2]
            partial_counter[p1][dir] += 1
            if dir == EndpointDirection.RIGHT:
                ingoing_edges[p1][p2] = get_right_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.LEFT:
                ingoing_edges[p1][p2] = get_left_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.TOP:
                ingoing_edges[p1][p2] = get_top_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])
            elif dir == EndpointDirection.BOTTOM:
                ingoing_edges[p1][p2] = get_bottom_edge_coord(
                    node, p1, partial_counter[p1], total_counter[p1])

    # order the left-entering ingoing edges better
    for p1 in ingoing_edges:
        vals = [(x, y) for x, y in ingoing_edges[p1].items() if y[0] == p1[0]]
        if len(vals) > 1:
            vals_x = [x[0] for x in vals]
            vals_y = [x[1] for x in vals]
            vals_x = sorted(vals_x)
            vals_y = sorted(vals_y)
            for i in range(len(vals_x)):
                ingoing_edges[p1][vals_x[i]] = vals_y[i]

    # set waypoints for edges
    for flow in flows:
        source = flow.get_source()
        target = flow.get_target()

        flow.del_waypoints()

        x_src = source.get_x()
        x_trg = target.get_x()
        y_src = source.get_y()
        y_trg = target.get_y()
        p1 = (x_src, y_src)
        p2 = (x_trg, y_trg)

        source_x = outgoing_edges[p1][p2][0]
        source_y = outgoing_edges[p1][p2][1]
        target_x = ingoing_edges[p2][p1][0]
        target_y = ingoing_edges[p2][p1][1]
        dir_source = outgoing_edges_dirs[p1][p2]
        dir_target = ingoing_edges_dirs[p2][p1]

        middle_x = (source_x + target_x) / 2.0
        middle_y = (source_y + target_y) / 2.0

        flow.add_waypoint((source_x, source_y))
        if dir_source in [EndpointDirection.LEFT, EndpointDirection.RIGHT]:
            if dir_target in [EndpointDirection.LEFT, EndpointDirection.RIGHT]:
                flow.add_waypoint((middle_x, source_y))
                flow.add_waypoint((middle_x, target_y))
            elif dir_target in [
                    EndpointDirection.TOP, EndpointDirection.BOTTOM
            ]:
                flow.add_waypoint((target_x, source_y))
        elif dir_source in [EndpointDirection.TOP, EndpointDirection.BOTTOM]:
            if dir_target in [EndpointDirection.TOP, EndpointDirection.BOTTOM]:
                flow.add_waypoint((source_x, middle_y))
                flow.add_waypoint((target_x, middle_y))
            elif dir_target in [
                    EndpointDirection.LEFT, EndpointDirection.RIGHT
            ]:
                flow.add_waypoint((source_x, target_y))

        flow.add_waypoint((target_x, target_y))

    return bpmn_graph