def get_boundaries_of_elements_in_dict(models_dict, clearance=0.):
    """ Get boundaries of all handed models

    The function checks all model meta data positions to increase boundary starting with a state or scoped variables.
    It is finally iterated over all states, data and logical port models and linkage if sufficient for respective
    graphical editor. At the end a clearance is added to the boundary if needed e.g. to secure size for opengl.

    :param models_dict: dict of all handed models
    :return: tuple of left, right, top and bottom value
    """

    # Determine initial outer coordinates
    right = 0.
    bottom = 0.
    if 'states' in models_dict and models_dict['states']:
        left = list(models_dict['states'].items())[0][1].get_meta_data_editor()['rel_pos'][0]
        top = list(models_dict['states'].items())[0][1].get_meta_data_editor()['rel_pos'][1]
    elif 'scoped_variables' in models_dict and models_dict['scoped_variables']:
        left = list(models_dict['scoped_variables'].items())[0][1].get_meta_data_editor()['inner_rel_pos'][0]
        top = list(models_dict['scoped_variables'].items())[0][1].get_meta_data_editor()['inner_rel_pos'][1]
    else:
        all_ports = list(models_dict['input_data_ports'].values()) + list(models_dict['output_data_ports'].values()) + \
                    list(models_dict['scoped_variables'].values()) + list(models_dict['outcomes'].values())
        if len(set([port_m.core_element.parent for port_m in all_ports])) == 1:
            logger.info("Only one parent {0} {1}".format(all_ports[0].core_element.parent, all_ports[0].parent.get_meta_data_editor()))
        if all_ports:
            left = all_ports[0].parent.get_meta_data_editor()['rel_pos'][0]
            top = all_ports[0].parent.get_meta_data_editor()['rel_pos'][1]
        else:
            raise ValueError("Get boundary method does not aspects all list elements empty in dictionary. {0}"
                             "".format(models_dict))

    def cal_max(max_x, max_y, rel_pos, size):
        max_x = size[0] + rel_pos[0] if size[0] + rel_pos[0] > max_x else max_x
        max_y = rel_pos[1] + size[1] if rel_pos[1] + size[1] > max_y else max_y
        return max_x, max_y

    def cal_min(min_x, min_y, rel_pos, size):
        min_x = rel_pos[0] if rel_pos[0] < min_x else min_x
        min_y = rel_pos[1] if rel_pos[1] < min_y else min_y
        return min_x, min_y

    # Finally take into account all relevant elements in models_dict
    # -> states, scoped variables (maybe input- and output- data ports) and transitions and data flows are relevant
    parts = ['states', 'transitions', 'data_flows']
    for key in parts:
        elems_dict = models_dict[key]
        rel_positions = []
        for model in elems_dict.values():
            _size = (0., 0.)
            if key == 'states':
                rel_positions = [model.get_meta_data_editor()['rel_pos']]
                _size = model.get_meta_data_editor()['size']
                # print(key, rel_positions, _size)
            elif key in ['scoped_variables', 'input_data_ports', 'output_data_ports']:
                rel_positions = [model.get_meta_data_editor()['inner_rel_pos']]
                # TODO check to take the ports size into account
                # print(key, rel_positions, _size)
            elif key in ['transitions', 'data_flows']:
                if key is "data_flows":
                    # take into account the meta data positions of opengl if there is some (always in opengl format)
                    rel_positions = mirror_waypoints(deepcopy(model.get_meta_data_editor()))['waypoints']
                else:
                    rel_positions = model.get_meta_data_editor()['waypoints']
                # print(key, rel_positions, _size, model.meta)

            for rel_position in rel_positions:
                # check for empty fields and ignore them at this point
                if not contains_geometric_info(rel_position):
                    continue
                right, bottom = cal_max(right, bottom, rel_position, _size)
                left, top = cal_min(left, top, rel_position, _size)
                # print("new edges:", left, right, top, bottom, key)

    # increase of boundary results into bigger estimated size and finally stronger reduction of original element sizes
    left, right, top, bottom = add_boundary_clearance(left, right, top, bottom, {'size': (0., 0.)}, clearance)
    return left, right, top, bottom