diff --git a/features/org.eclipse.elk.algorithms.feature/feature.xml b/features/org.eclipse.elk.algorithms.feature/feature.xml
index 6a35894ebc..f02bc58ddc 100644
--- a/features/org.eclipse.elk.algorithms.feature/feature.xml
+++ b/features/org.eclipse.elk.algorithms.feature/feature.xml
@@ -101,5 +101,12 @@ SPDX-License-Identifier: EPL-2.0
install-size="0"
version="0.0.0"
unpack="false"/>
+
+
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/.classpath b/plugins/org.eclipse.elk.alg.vertiflex/.classpath
new file mode 100644
index 0000000000..f0c5549859
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/.project b/plugins/org.eclipse.elk.alg.vertiflex/.project
new file mode 100644
index 0000000000..bb53ae1b34
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/.project
@@ -0,0 +1,46 @@
+
+
+ org.eclipse.elk.alg.vertiflex
+
+
+
+
+
+ org.eclipse.xtext.ui.shared.xtextBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ net.sf.eclipsecs.core.CheckstyleBuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ net.sf.eclipsecs.core.CheckstyleNature
+ org.eclipse.xtext.ui.shared.xtextNature
+
+
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..5ed2aebf75
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,297 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=120
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.ui.prefs b/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000..d529be02be
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+formatter_profile=_Elk
+formatter_settings_version=12
+org.eclipse.jdt.ui.javadoc=true
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//*******************************************************************************\n * Copyright (c) ${year} ${user} and others.\n * \n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License 2.0 which is available at\n * http://www.eclipse.org/legal/epl-2.0.\n * \n * SPDX-License-Identifier: EPL-2.0 \n *******************************************************************************//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//* (non-Javadoc)\n * ${see_to_overridden}\n *//**\n * ${tags}\n * ${see_to_target}\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/META-INF/MANIFEST.MF b/plugins/org.eclipse.elk.alg.vertiflex/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..492ffb38db
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/META-INF/MANIFEST.MF
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+Automatic-Module-Name: org.eclipse.elk.alg.vertiflex
+Bundle-ManifestVersion: 2
+Bundle-Name: VertiFlex Tree Layout Algorithm
+Bundle-SymbolicName: org.eclipse.elk.alg.vertiflex;singleton:=true
+Bundle-Version: 0.10.0.qualifier
+Bundle-Vendor: Eclipse Modeling Project
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: com.google.guava,
+ org.eclipse.elk.alg.common,
+ org.eclipse.elk.core,
+ org.eclipse.elk.graph
+Export-Package: org.eclipse.elk.alg.vertiflex,
+ org.eclipse.elk.alg.vertiflex.options
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/about.html b/plugins/org.eclipse.elk.alg.vertiflex/about.html
new file mode 100644
index 0000000000..7a303200e8
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/about.html
@@ -0,0 +1,36 @@
+
+
+
+
+About
+
+
+ About This Content
+
+ October 11, 2023
+ License
+
+
+ The Eclipse Foundation makes available all content in this plug-in
+ ("Content"). Unless otherwise indicated below, the Content
+ is provided to you under the terms and conditions of the Eclipse
+ Public License Version 2.0 ("EPL"). A copy of the EPL is
+ available at http://www.eclipse.org/legal/epl-2.0.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
+
+ If you did not receive this Content directly from the Eclipse
+ Foundation, the Content is being redistributed by another party
+ ("Redistributor") and different terms and conditions may
+ apply to your use of any object code in the Content. Check the
+ Redistributor's license that was provided with the Content. If no such
+ license exists, contact the Redistributor. Unless otherwise indicated
+ below, the terms and conditions of the EPL still apply to any source
+ code in the Content and such source code may be obtained at http://www.eclipse.org.
+
+
+
+
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/build.properties b/plugins/org.eclipse.elk.alg.vertiflex/build.properties
new file mode 100644
index 0000000000..96e1399046
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/build.properties
@@ -0,0 +1,16 @@
+###############################################################################
+# Copyright (c) 2023 Kiel University and others.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License 2.0 which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# SPDX-License-Identifier: EPL-2.0
+###############################################################################
+source.. = src/,\
+ src-gen/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ about.html
+src.includes = about.html
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/pom.xml b/plugins/org.eclipse.elk.alg.vertiflex/pom.xml
new file mode 100644
index 0000000000..7f65238f92
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+ 4.0.0
+
+ org.eclipse.elk
+ parent
+ 0.10.0-SNAPSHOT
+ ../../build/pom.xml
+
+
+ org.eclipse.elk
+ org.eclipse.elk.alg.vertiflex
+ ELK Y Constraint Tree Layout Algorithm
+ 0.10.0-SNAPSHOT
+ Tree drawing algorithm with y-level constraints.
+ eclipse-plugin
+
+
+
+ org.eclipse.elk
+ org.eclipse.elk.core
+ 0.10.0-SNAPSHOT
+
+
+ org.eclipse.elk
+ org.eclipse.elk.alg.common
+ 0.10.0-SNAPSHOT
+
+
+
+
+
+
+
+ org.eclipse.xtext
+ xtext-maven-plugin
+
+
+
+
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/META-INF/services/org.eclipse.elk.core.data.ILayoutMetaDataProvider b/plugins/org.eclipse.elk.alg.vertiflex/src/META-INF/services/org.eclipse.elk.core.data.ILayoutMetaDataProvider
new file mode 100644
index 0000000000..20f68e7073
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/META-INF/services/org.eclipse.elk.core.data.ILayoutMetaDataProvider
@@ -0,0 +1 @@
+org.eclipse.elk.alg.vertiflex.options.VertiFlexMetaDataProvider
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/EdgeRoutingStrategy.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/EdgeRoutingStrategy.java
new file mode 100644
index 0000000000..c5b02ccc6e
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/EdgeRoutingStrategy.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex;
+
+/**
+ * Strategies for routing edges in the tree layout.
+ *
+ */
+public enum EdgeRoutingStrategy {
+
+ /**
+ * Straight lines between nodes.
+ */
+ STRAIGHT,
+
+ /**
+ * Allow one bend point in edges to enable more compact layouts while also maintaining the node model order.
+ */
+ BEND
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/InternalProperties.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/InternalProperties.java
new file mode 100644
index 0000000000..349ef309cc
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/InternalProperties.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex;
+
+import org.eclipse.elk.alg.vertiflex.p2relative.OutlineNode;
+import org.eclipse.elk.graph.ElkNode;
+import org.eclipse.elk.graph.properties.IProperty;
+import org.eclipse.elk.graph.properties.Property;
+
+/**
+ * The internal properties of the tree layouter. Properties that are used internally
+ * but do not need to be public are stored here.
+ *
+ */
+public final class InternalProperties {
+
+ /**
+ * Private constructor to prevent initialization.
+ */
+ private InternalProperties() { }
+
+ /**
+ * Defines the left outline of this subtree.
+ */
+ public static final IProperty LEFT_OUTLINE = new Property("LEFT_OUTLINE");
+
+ /**
+ * Defines the right outline of this subtree.
+ */
+ public static final IProperty RIGHT_OUTLINE = new Property("RIGHT_OUTLINE");
+
+ /**
+ * Defines the maximum depth of outlines. This is the lowest point of the outline in the tree layout.
+ */
+ public static final IProperty OUTLINE_MAX_DEPTH = new Property("OUTLINE_MAX_DEPTH");
+
+ /** Defines the canvas of the tree. */
+ public static final IProperty MIN_X = new Property("MIN_X");
+
+ /** Defines the canvas of the tree. */
+ public static final IProperty MAX_X = new Property("MAX_X");
+
+ /** Defines the canvas of the tree. */
+ public static final IProperty MIN_Y = new Property("MIN_Y");
+
+ /** Defines the canvas of the tree. */
+ public static final IProperty MAX_Y = new Property("MAX_Y");
+
+ /** The root node of the graph. */
+ public static final IProperty ROOT_NODE = new Property("root");
+
+ /**
+ * Defines the bendpoint of an edge.
+ */
+ public static final IProperty EDGE_BEND_HEIGHT = new Property("EDGE_BEND_HEIGHT");
+
+ /**
+ * Stores the model order of nodes. Smaller values come before larger values.
+ */
+ public static final IProperty NODE_MODEL_ORDER = new Property("Node Model Order");
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlex.melk b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlex.melk
new file mode 100644
index 0000000000..89336435ff
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlex.melk
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex
+
+import org.eclipse.elk.alg.vertiflex.EdgeRoutingStrategy
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutProvider
+import org.eclipse.elk.core.math.ElkPadding
+
+/**
+ * Declarations for the ELK VertiFlex tree layout algorithm.
+ */
+bundle {
+ metadataClass options.VertiFlexMetaDataProvider
+ idPrefix org.eclipse.elk.vertiflex
+}
+
+algorithm vertiflex(VertiFlexLayoutProvider) {
+ label "ELK VertiFlex"
+ description
+ "Tree layout algorithm that allows defining set vertical positions for nodes
+ rather than automatically placing nodes on levels according to their topology."
+ metadataClass options.VertiFlexOptions
+ category org.eclipse.elk.tree
+ features multi_edges, edge_labels
+ supports org.eclipse.elk.spacing.nodeNode = 20
+ supports org.eclipse.elk.padding = new ElkPadding(5)
+ supports org.eclipse.elk.interactive
+ supports org.eclipse.elk.portConstraints
+ supports org.eclipse.elk.edgeLabels.inline = false
+ // Common node micro layout
+ supports org.eclipse.elk.omitNodeMicroLayout
+ supports org.eclipse.elk.margins
+ // Algorithm specific properties
+ supports verticalConstraint
+ supports layoutStrategy
+ supports layerDistance
+ supports considerNodeModelOrder
+
+}
+
+option verticalConstraint: double {
+ label "Fixed vertical position"
+ description
+ "The Y position that the node should be fixed at."
+ targets nodes
+}
+
+option layoutStrategy: EdgeRoutingStrategy {
+ label "Edge layout strategy"
+ description
+ "Strategy for the layout of the children. 'straight' for straight line drawings, 'bend' for a possible bend.
+ When straight edges are prioritized the nodes will be reordered in order to guarantee that straight edges are
+ possible. If bend points are enabled on the other hand, the given model order of the nodes is maintained and
+ bend points are introduced to prevent edge node overlaps."
+ targets nodes
+ default = EdgeRoutingStrategy.STRAIGHT
+}
+
+option layerDistance: double {
+ label "Layer distance"
+ description
+ "The distance to use between nodes of different layers if no vertical constraints are set."
+ targets parents
+ default = 50.0
+}
+
+option considerNodeModelOrder: boolean {
+ label "Consider node model order"
+ description
+ "Consider node model as a secondary criterion when using straight line routing."
+ targets parents
+ default = true
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutPhases.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutPhases.java
new file mode 100644
index 0000000000..0060d75501
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutPhases.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex;
+
+/**
+ * This algorithm's layout phases. Each phase has its own strategy factory.
+ */
+public enum VertiFlexLayoutPhases {
+
+ /** Phase 1. Vertical placement of nodes.*/
+ P1_NODE_Y_PLACEMENT,
+
+ /** Phase 2. Horizontal placement of nodes.*/
+ P2_NODE_RELATIVE_PLACEMENT,
+
+ /** Phase 3. Computation of absolute coordinates.*/
+ P3_NODE_ABSOLUTE_PLACEMENT,
+
+ /** Phase 4. Edge routing.*/
+ P4_EDGE_ROUTING;
+
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutProvider.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutProvider.java
new file mode 100644
index 0000000000..643a528f1b
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexLayoutProvider.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2024 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex;
+
+import java.util.List;
+
+import org.eclipse.elk.alg.common.NodeMicroLayout;
+import org.eclipse.elk.alg.vertiflex.options.VertiFlexOptions;
+import org.eclipse.elk.alg.vertiflex.p1yplacement.NodeYPlacerStrategy;
+import org.eclipse.elk.alg.vertiflex.p2relative.RelativeXPlacerStrategy;
+import org.eclipse.elk.alg.vertiflex.p3absolute.AbsoluteXPlacerStrategy;
+import org.eclipse.elk.alg.vertiflex.p4edgerouting.EdgerouterStrategy;
+import org.eclipse.elk.core.AbstractLayoutProvider;
+import org.eclipse.elk.core.UnsupportedConfigurationException;
+import org.eclipse.elk.core.alg.AlgorithmAssembler;
+import org.eclipse.elk.core.alg.ILayoutProcessor;
+import org.eclipse.elk.core.math.ElkMargin;
+import org.eclipse.elk.core.math.ElkPadding;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkEdge;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Layout provider for the y constraint tree layout algorithms.
+ */
+public final class VertiFlexLayoutProvider extends AbstractLayoutProvider {
+
+
+ private final AlgorithmAssembler algorithmAssembler =
+ AlgorithmAssembler.create(VertiFlexLayoutPhases.class);
+
+ private double nodeNodeSpacing;
+
+ @Override
+ public void layout(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+ List> algorithm = assembleAlgorithm(graph);
+
+ progressMonitor.begin("Tree layout", algorithm.size());
+
+ nodeNodeSpacing = graph.getProperty(CoreOptions.SPACING_NODE_NODE);
+
+ // if requested, compute nodes's dimensions, place node labels, ports, port labels, etc.
+ if (!graph.getProperty(VertiFlexOptions.OMIT_NODE_MICRO_LAYOUT)) {
+ NodeMicroLayout.forGraph(graph)
+ .execute();
+ }
+
+ // pre calculate the root node and save it
+ ElkNode root = VertiFlexUtil.findRoot(graph);
+ graph.setProperty(InternalProperties.ROOT_NODE, root);
+ if (root == null) {
+ throw new UnsupportedConfigurationException("The given graph is not a tree!");
+ }
+
+ for (ElkNode child : graph.getChildren()) {
+ int numberOfParents;
+ numberOfParents = child.getIncomingEdges().size();
+ if (numberOfParents > 1) {
+ throw new UnsupportedConfigurationException("The given graph is not an acyclic tree!");
+ }
+
+ // reset position
+ child.setLocation(0, 0);
+ }
+
+ // check that vertical constraints are ordered in valid manner i.e. children always have higher vertical
+ // constraints than their parents
+ checkVerticalConstraintValidity(root, 0);
+
+ // store model order
+ int count = 0;
+ for (ElkNode node : graph.getChildren()) {
+ node.setProperty(InternalProperties.NODE_MODEL_ORDER, count);
+ count += 1;
+ }
+
+ for (ILayoutProcessor processor : algorithm) {
+ processor.process(graph, progressMonitor.subTask(1));
+ }
+
+ setGraphSize(graph);
+
+ progressMonitor.done();
+ }
+
+ /**
+ * Configure the layout provider by assembling different layout processors.
+ *
+ * @param graph The graph which shall be laid out.
+ * @return The list of assembled layout processors.
+ */
+ public List> assembleAlgorithm(final ElkNode graph) {
+ algorithmAssembler.reset();
+
+ // Configure phases
+ algorithmAssembler.setPhase(VertiFlexLayoutPhases.P1_NODE_Y_PLACEMENT,
+ NodeYPlacerStrategy.SIMPLE_Y_PLACING);
+ algorithmAssembler.setPhase(VertiFlexLayoutPhases.P2_NODE_RELATIVE_PLACEMENT,
+ RelativeXPlacerStrategy.SIMPLE_X_PLACING);
+ algorithmAssembler.setPhase(VertiFlexLayoutPhases.P3_NODE_ABSOLUTE_PLACEMENT,
+ AbsoluteXPlacerStrategy.ABSOLUTE_XPLACING);
+
+ EdgerouterStrategy routerStrategy;
+ switch (graph.getProperty(VertiFlexOptions.LAYOUT_STRATEGY)) {
+ case BEND:
+ routerStrategy = EdgerouterStrategy.BEND_ROUTING;
+ break;
+ case STRAIGHT:
+ default:
+ routerStrategy = EdgerouterStrategy.DIRECT_ROUTING;
+ break;
+
+ }
+ algorithmAssembler.setPhase(VertiFlexLayoutPhases.P4_EDGE_ROUTING, routerStrategy);
+
+ // Assemble the algorithm
+ return algorithmAssembler.build(graph);
+ }
+
+ /** Checks whether a vertical constraint is larger than the constraints set by any ancestor nodes.*/
+ private void checkVerticalConstraintValidity(final ElkNode root, final double currentMinConstraint) {
+
+ double rootHeight;
+ if (root.hasProperty(VertiFlexOptions.VERTICAL_CONSTRAINT)) {
+ rootHeight = root.getProperty(VertiFlexOptions.VERTICAL_CONSTRAINT);
+ } else {
+ rootHeight = currentMinConstraint;
+ }
+
+ double newMinConstraint = rootHeight + root.getHeight()
+ + Math.max(root.getProperty(CoreOptions.MARGINS).bottom, nodeNodeSpacing);
+
+ for (ElkEdge outgoingEdge : root.getOutgoingEdges()) {
+ ElkNode child = (ElkNode) outgoingEdge.getTargets().get(0);
+ if (child.hasProperty(VertiFlexOptions.VERTICAL_CONSTRAINT)) {
+ if (newMinConstraint > child.getProperty(VertiFlexOptions.VERTICAL_CONSTRAINT)
+ + child.getProperty(CoreOptions.MARGINS).top) {
+ throw new UnsupportedConfigurationException("Invalid vertical constraints. Node "
+ + child.getIdentifier() + " has a vertical constraint that is too low for its ancestors.");
+ }
+ }
+ }
+ for (ElkEdge outgoingEdge : root.getOutgoingEdges()) {
+ ElkNode child = (ElkNode) outgoingEdge.getTargets().get(0);
+ checkVerticalConstraintValidity(child, newMinConstraint);
+ }
+ }
+
+ /** Computes the space occupied by the layout and sets the graph size accordingly. */
+ private void setGraphSize(final ElkNode graph) {
+ ElkPadding padding = graph.getProperty(CoreOptions.PADDING);
+
+ double maxX = 0.0;
+ double maxY = 0.0;
+ for (ElkNode node : graph.getChildren()) {
+ ElkMargin margin = node.getProperty(CoreOptions.MARGINS);
+
+ if (maxX < node.getX() + node.getWidth() + margin.right) {
+ maxX = node.getX() + node.getWidth() + margin.right;
+ }
+ if (maxY < node.getY() + node.getHeight() + margin.bottom) {
+ maxY = node.getY() + node.getHeight() + margin.bottom;
+ }
+ }
+
+ graph.setWidth(maxX + padding.right);
+ graph.setHeight(maxY + padding.bottom);
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexUtil.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexUtil.java
new file mode 100644
index 0000000000..4b48909be7
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/VertiFlexUtil.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex;
+
+import org.eclipse.elk.graph.ElkEdge;
+import org.eclipse.elk.graph.ElkNode;
+import org.eclipse.elk.graph.util.ElkGraphUtil;
+
+/** A class for smaller, independent calculation units. */
+public final class VertiFlexUtil {
+
+ /**
+ * Private constructor to prevent initialization.
+ */
+ private VertiFlexUtil() { }
+
+ /**
+ * Computes the root node of a graph.
+ * @param graph
+ * @return Root node of graph.
+ */
+ public static ElkNode findRoot(final ElkNode graph) {
+ for (ElkNode child : graph.getChildren()) {
+ Iterable incomingEdges = ElkGraphUtil.allIncomingEdges(child);
+ if (!incomingEdges.iterator().hasNext()) {
+ return child;
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacer.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacer.java
new file mode 100644
index 0000000000..d0a4ce8842
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacer.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p1yplacement;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.alg.vertiflex.options.VertiFlexOptions;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Node placer to position nodes vertically. Nodes that have a vertical constraint are placed according to that
+ * constraint and other nodes are positioned automatically according to their position in the tree.
+ *
+ */
+public class NodeYPlacer implements ILayoutPhase {
+
+ private double layerDistance;
+ private double nodeNodeSpacing;
+
+ @Override
+ public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+
+ progressMonitor.begin("YPlacer", 1);
+
+ layerDistance = graph.getProperty(VertiFlexOptions.LAYER_DISTANCE);
+ nodeNodeSpacing = graph.getProperty(CoreOptions.SPACING_NODE_NODE);
+
+ if (!graph.getChildren().isEmpty()) {
+ ElkNode parent = graph.getProperty(InternalProperties.ROOT_NODE);
+ setYLevels(parent, 0.0);
+ }
+
+ progressMonitor.done();
+ }
+
+ /**
+ * A Method to set the absolute Y coordinates of the nodes.
+ * Uses a default distance between the nodes.
+ * @param node and it's children are getting updated Y-coords.
+ * @param minHeight: Node gets minimum this height.
+ */
+ private void setYLevels(final ElkNode node, double minHeight) {
+ if (node.hasProperty(VertiFlexOptions.VERTICAL_CONSTRAINT)) {
+ minHeight = node.getProperty(VertiFlexOptions.VERTICAL_CONSTRAINT);
+ }
+ node.setY(minHeight);
+ double newMinHeight = minHeight + layerDistance + node.getHeight()
+ + Math.max(node.getProperty(CoreOptions.MARGINS).bottom, nodeNodeSpacing);
+ for (int i = 0; i < node.getOutgoingEdges().size(); i++) {
+ ElkNode child = (ElkNode) node.getOutgoingEdges().get(i).getTargets().get(0);
+ setYLevels(child, newMinHeight);
+ }
+ }
+
+ @Override
+ public LayoutProcessorConfiguration
+ getLayoutProcessorConfiguration(final ElkNode graph) {
+ return null;
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacerStrategy.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacerStrategy.java
new file mode 100644
index 0000000000..0386907623
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p1yplacement/NodeYPlacerStrategy.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p1yplacement;
+
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.ILayoutPhaseFactory;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Vertical node placement strategies.
+ *
+ */
+public enum NodeYPlacerStrategy implements ILayoutPhaseFactory {
+
+ /**
+ * Simple strategy for setting y coordinates of nodes. Vertical constraints are considered and if none are defined
+ * the fallback is to compute a position based on the node's location in the tree.
+ */
+ SIMPLE_Y_PLACING;
+
+ @Override
+ public ILayoutPhase create() {
+ switch (this) {
+ case SIMPLE_Y_PLACING:
+ return new NodeYPlacer();
+
+ default:
+ throw new IllegalArgumentException(
+ "No implementation is available for the node placer " + this.toString());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/NodeComparator.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/NodeComparator.java
new file mode 100644
index 0000000000..72f804c65a
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/NodeComparator.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p2relative;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Comparator for sorting ElkNodes according to their y positions and model order.
+ *
+ */
+class NodeComparator implements java.util.Comparator {
+
+ private boolean invert = false;
+
+ /** Default constructor */
+ public NodeComparator() {}
+
+ /** Constructor for inverted comparator. */
+ public NodeComparator(boolean invert) {
+ this.invert = invert;
+ }
+
+ @Override
+ public int compare(final ElkNode a, final ElkNode b) {
+
+ int sortYresult;
+ if (!invert) {
+ sortYresult = Double.compare(a.getY(), b.getY());
+ } else {
+ sortYresult = Double.compare(b.getY(), a.getY());
+ }
+ if (sortYresult == 0) {
+ int intSortresult = Integer.compare(a.getProperty(InternalProperties.NODE_MODEL_ORDER),
+ b.getProperty(InternalProperties.NODE_MODEL_ORDER));
+ return intSortresult;
+ } else {
+ return sortYresult;
+ }
+ }
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/OutlineNode.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/OutlineNode.java
new file mode 100644
index 0000000000..1403dcdb1c
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/OutlineNode.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p2relative;
+
+/**
+ * An outline node is the basic building block of an outline.
+ * Outline nodes define the points on which the outline lies.
+ * Each outline node has a position and a reference to the next outline node.
+ *
+ */
+public class OutlineNode {
+
+ /** X coordinates are maintained as relative coordinates with respect to their ancestors. */
+ private double relativeX;
+ /** Y coordinates are maintained as absolute coordinates that stem from the given vertical constraints. */
+ private double absoluteY;
+ private OutlineNode next;
+
+ public OutlineNode(final double relativeX, final double absoluteY, final OutlineNode next){
+ this.setRelativeX(relativeX);
+ this.setAbsoluteY(absoluteY);
+ this.setNext(next);
+ }
+
+ /**
+ * @return the absoluteY
+ */
+ public double getAbsoluteY() {
+ return absoluteY;
+ }
+
+ /**
+ * @param absoluteY the absoluteY to set
+ */
+ public void setAbsoluteY(final double absoluteY) {
+ this.absoluteY = absoluteY;
+ }
+
+ /**
+ * @return the relativeX
+ */
+ public double getRelativeX() {
+ return relativeX;
+ }
+
+ /**
+ * @param relativeX the relativeX to set
+ */
+ public void setRelativeX(final double relativeX) {
+ this.relativeX = relativeX;
+ }
+
+ /**
+ * @return the next
+ */
+ public OutlineNode getNext() {
+ return next;
+ }
+
+ /**
+ * @param next the next to set
+ */
+ public void setNext(final OutlineNode next) {
+ this.next = next;
+ }
+
+ /**
+ * @return a string representation of this objects relative coordinates
+ */
+ public String toString() {
+ return "X:" + relativeX + ", Y:" + absoluteY;
+ }
+
+ /**
+ *
+ * @return if this OutlineNode has no "next"
+ */
+ public boolean isLast() {
+ return next == null;
+ }
+
+ /**
+ * Print a full outline.
+ */
+ public void printFullOutline() {
+ System.out.println(this.toString());
+ if (!this.isLast()) {
+ this.getNext().printFullOutline();
+ }
+ }
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacer.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacer.java
new file mode 100644
index 0000000000..eb4bdffc74
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacer.java
@@ -0,0 +1,670 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p2relative;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.alg.vertiflex.options.VertiFlexOptions;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
+import org.eclipse.elk.core.math.ElkMargin;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.core.util.Pair;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Node placer that positions nodes horizontally using coordinates that are relative to parent nodes.
+ *
+ */
+public class RelativeXPlacer implements ILayoutPhase {
+
+ private double spacingNodeNode;
+ private boolean considerNodeModelOrder;
+
+ // a constant for moving every Outline to a minimal y-pos
+ private static final double MINIMAL_Y = -100.0;
+
+ @Override
+ public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+
+ progressMonitor.begin("XPlacer", 1);
+
+ spacingNodeNode = graph.getProperty(CoreOptions.SPACING_NODE_NODE);
+ considerNodeModelOrder = graph.getProperty(VertiFlexOptions.CONSIDER_NODE_MODEL_ORDER);
+
+ if (!graph.getChildren().isEmpty()) {
+ ElkNode parent = graph.getProperty(InternalProperties.ROOT_NODE);
+
+ switch (graph.getProperty(VertiFlexOptions.LAYOUT_STRATEGY)) {
+ case STRAIGHT:
+ recursiveStraightlinePlacement(parent);
+ break;
+ case BEND:
+ recursiveBentlinePlacement(parent);
+ break;
+ default:
+ break;
+ }
+ }
+
+ progressMonitor.done();
+ }
+
+ /** Computes the distance between two outlines. */
+ private double outlineDistance(final OutlineNode outline1, final OutlineNode outline2) {
+
+ OutlineNode changedOutline1 = new OutlineNode(outline1.getRelativeX(), MINIMAL_Y,
+ new OutlineNode(0.0, outline1.getAbsoluteY(), outline1.getNext()));
+ OutlineNode changedOutline2 = new OutlineNode(outline2.getRelativeX(), MINIMAL_Y,
+ new OutlineNode(0.0, outline2.getAbsoluteY(), outline2.getNext()));
+
+ // the return value
+ double dist = changedOutline1.getRelativeX() - changedOutline2.getRelativeX();
+
+ OutlineNode o1;
+ OutlineNode o2;
+ double x1;
+ double x2;
+ double deltaX, deltaY;
+ double newdist;
+
+
+ // first run (compare points of o1 with o2)
+ o1 = changedOutline1;
+ o2 = changedOutline2;
+ x1 = o1.getRelativeX();
+ x2 = o2.getRelativeX();
+ while (o1 != null && !o2.isLast()) {
+ if (o2.getNext().getAbsoluteY() > o1.getAbsoluteY()) {
+ // now we compare
+ deltaX = o2.getNext().getRelativeX();
+ deltaY = o2.getNext().getAbsoluteY() - o2.getAbsoluteY();
+ newdist = x1 - x2 - ((o1.getAbsoluteY() - o2.getAbsoluteY()) * deltaX) / deltaY;
+
+ dist = Math.max(dist, newdist);
+
+ // now change o1
+ o1 = o1.getNext();
+ if (o1 != null) {
+ x1 += o1.getRelativeX();
+ }
+ } else {
+ o2 = o2.getNext();
+ x2 += o2.getRelativeX();
+ }
+ }
+
+ // second run (compare points of o2 with o1)
+ o1 = changedOutline1;
+ o2 = changedOutline2;
+ x1 = o1.getRelativeX();
+ x2 = o2.getRelativeX();
+ while (o2 != null && !o1.isLast()) {
+ if (o1.getNext().getAbsoluteY() > o2.getAbsoluteY()) {
+ // now we compare
+ deltaX = o1.getNext().getRelativeX();
+ deltaY = o1.getNext().getAbsoluteY() - o1.getAbsoluteY();
+ newdist = x1 - x2 + ((o2.getAbsoluteY() - o1.getAbsoluteY()) * deltaX) / deltaY;
+
+ dist = Math.max(dist, newdist);
+
+ // now change o2
+ o2 = o2.getNext();
+ if (o2 != null) {
+ x2 += o2.getRelativeX();
+ }
+ } else {
+ o1 = o1.getNext();
+ x1 += o1.getRelativeX();
+ }
+
+ }
+
+ return dist;
+ }
+
+ /**
+ * This is the recursive function that calculates the layout for one node and it's children.
+ * Children are placed such that straight edges can later be drawn from their parent to each of them.
+ *
+ * If ConsiderModelOrder is set to false, all children are arranged in a semi-circle with the parent
+ * initially positioned above the lowest child and then shifted toward the center of the children as
+ * far as possible.
+ *
+ * If ConsiderModelOrder is set to true, groups of children are arranged such that they maintain their
+ * inherent model order. Furthermore, the reading direction is oriented left to right and top to bottom
+ * as far as possible without violating the vertical position constraints and the straight edge routing
+ * requirement.
+ */
+ private void recursiveStraightlinePlacement(final ElkNode graph) {
+
+ makeSimpleOutlines(graph);
+
+ if (!graph.getOutgoingEdges().isEmpty()) {
+
+ // get all children
+ List children = new ArrayList<>();
+ for (int i = 0; i < graph.getOutgoingEdges().size(); i++) {
+ ElkNode child = (ElkNode) graph.getOutgoingEdges().get(i).getTargets().get(0);
+ recursiveStraightlinePlacement(child);
+ children.add(child);
+ }
+ // now the children of this node get sorted to form a semi-circle. This allows routing straight edges
+ // without overlaps while keeping the layout relatively compact.
+ sortSubTrees(children);
+
+ // now the children get stuffed together, using the outlines.
+ for (int i = 0; i < children.size() - 1; i++) {
+ bundleChildren(children.get(0), children.get(i), children.get(i + 1));
+ }
+
+ // now we need to move the root to the middle of the nodes.
+ // we calculate the point of the child with the lowest y-position to avoid overlapping.
+ // if there is more than one lowest child, the root will be positioned in the middle of them.
+ int pos = 0;
+ double maxDepth = 0.0;
+ int maxDepthStartPos = 0;
+ while (pos < children.size() && children.get(pos).getY() >= maxDepth) {
+ if (children.get(pos).getY() > maxDepth) {
+ maxDepthStartPos = pos;
+ maxDepth = children.get(pos).getY();
+ }
+ pos += 1;
+ }
+ double moveRoot = 0.0;
+ if (pos > 0) {
+ moveRoot = (children.get(maxDepthStartPos).getX() + children.get(pos - 1).getX()) / 2.0
+ - graph.getX();
+ }
+
+ if (!graph.getProperty(VertiFlexOptions.CONSIDER_NODE_MODEL_ORDER)) {
+ double betterMoveRoot = (children.get(0).getX() + children.get(children.size() - 1).getX()
+ + children.get(children.size() - 1).getWidth() - graph.getWidth()) / 2.0 - graph.getX();
+ double newMoveRoot;
+
+ if (betterMoveRoot < moveRoot) {
+ OutlineNode rightOutline;
+ double rightOutlineX, posX;
+ for (int i = 0; i < maxDepthStartPos; i++) {
+ for (int j = i + 1; j < maxDepthStartPos + 1; j++) {
+ rightOutline = children.get(i).getProperty(InternalProperties.RIGHT_OUTLINE);
+ rightOutlineX = children.get(i).getX() + rightOutline.getRelativeX();
+ posX = children.get(j).getX() + children.get(j).getWidth() / 2.0;
+ while (rightOutline != null && rightOutline.getAbsoluteY() < maxDepth) {
+
+ newMoveRoot = posX - graph.getWidth() / 2.0 + (posX - rightOutlineX) * ((graph.getY()
+ + graph.getHeight()) - maxDepth) / (maxDepth - rightOutline.getAbsoluteY());
+ betterMoveRoot = Math.max(betterMoveRoot, newMoveRoot);
+
+ rightOutline = rightOutline.getNext();
+ if (rightOutline != null) {
+ rightOutlineX += rightOutline.getRelativeX();
+ }
+ }
+ }
+ }
+ moveRoot = betterMoveRoot;
+ }
+
+ if (betterMoveRoot > moveRoot) {
+ OutlineNode leftOutline;
+ double leftOutlineX, posX;
+ for (int i = pos; i < children.size(); i++) {
+ for (int j = pos - 1; j < i; j++) {
+ leftOutline = children.get(i).getProperty(InternalProperties.LEFT_OUTLINE);
+ leftOutlineX = children.get(i).getX() + leftOutline.getRelativeX();
+ posX = children.get(j).getX() + children.get(j).getWidth() / 2.0;
+ while (leftOutline != null && leftOutline.getAbsoluteY() < maxDepth) {
+
+ newMoveRoot = posX - graph.getWidth() / 2.0 + (posX - leftOutlineX) * ((graph.getY()
+ + graph.getHeight()) - maxDepth) / (maxDepth - leftOutline.getAbsoluteY());
+ betterMoveRoot = Math.min(betterMoveRoot, newMoveRoot);
+
+ leftOutline = leftOutline.getNext();
+ if (leftOutline != null) {
+ leftOutlineX += leftOutline.getRelativeX();
+ }
+ }
+ }
+ }
+ moveRoot = betterMoveRoot;
+ }
+ }
+
+ for (ElkNode child: children) {
+ child.setX(child.getX() - moveRoot);
+ }
+
+
+ double newX;
+ // left outline update
+ OutlineNode graphLeftOutline = graph.getProperty(InternalProperties.LEFT_OUTLINE);
+ OutlineNode leftChildOutline = children.get(0).getProperty(InternalProperties.LEFT_OUTLINE);
+
+ newX = children.get(0).getX() + leftChildOutline.getRelativeX() - graphLeftOutline.getRelativeX();
+ graphLeftOutline.getNext().getNext().getNext().setNext(
+ new OutlineNode(newX, leftChildOutline.getAbsoluteY(), leftChildOutline.getNext()));
+
+ // right outline update
+ OutlineNode graphRightOutline = graph.getProperty(InternalProperties.RIGHT_OUTLINE);
+ OutlineNode rightChildOutline = children.get(children.size() - 1)
+ .getProperty(InternalProperties.RIGHT_OUTLINE);
+
+ newX = children.get(children.size() - 1).getX()
+ + rightChildOutline.getRelativeX() - graphRightOutline.getRelativeX();
+ graphRightOutline.getNext().getNext().getNext().setNext(
+ new OutlineNode(newX, rightChildOutline.getAbsoluteY(), rightChildOutline.getNext()));
+
+ // update outlineMaxY
+ // update min und max for x and y
+ for (ElkNode child: children) {
+ graph.setProperty(InternalProperties.OUTLINE_MAX_DEPTH, Math.max(graph.getProperty(InternalProperties.
+ OUTLINE_MAX_DEPTH), child.getProperty(InternalProperties.OUTLINE_MAX_DEPTH)));
+ graph.setProperty(InternalProperties.MIN_X, Math.min(graph.getProperty(InternalProperties.MIN_X),
+ child.getX() + child.getProperty(InternalProperties.MIN_X)));
+ graph.setProperty(InternalProperties.MAX_X, Math.max(graph.getProperty(InternalProperties.MAX_X),
+ child.getX() + child.getProperty(InternalProperties.MAX_X)));
+ }
+ graph.setProperty(InternalProperties.MAX_Y, graph.getProperty(InternalProperties.OUTLINE_MAX_DEPTH));
+
+ }
+ }
+
+ /**
+ * Place nodes while maintaining model order and computing bendpoints for the later edges.
+ * The model order is fully kept intact and bendpoints for the edges are computed so that
+ * overlap free edge routing is still possible.
+ */
+ private void recursiveBentlinePlacement(final ElkNode graph) {
+
+ // set up initial outlines for all nodes
+ makeSimpleOutlines(graph);
+
+ // termination condition for the recursion, while a node has children continue
+ if (!graph.getOutgoingEdges().isEmpty()) {
+
+ // get all children
+ List children = new ArrayList<>();
+ for (int i = 0; i < graph.getOutgoingEdges().size(); i++) {
+ ElkNode child = (ElkNode) graph.getOutgoingEdges().get(i).getTargets().get(0);
+ recursiveBentlinePlacement(child);
+ children.add(child);
+ }
+
+ int childrenSize = children.size();
+
+
+ // now the children get stuffed together, using the outlines.
+ for (int i = 0; i < children.size() - 1; i++) {
+ bundleChildren(children.get(0), children.get(i), children.get(i + 1));
+ }
+
+ double moveRoot = (children.get(0).getX() + children.get(0).getWidth() / 2.0
+ + children.get(children.size() - 1).getX() + children.get(children.size() - 1).getWidth() / 2.0
+ - graph.getWidth()) / 2.0 - graph.getX();
+
+ for (ElkNode child: children) {
+ child.setX(child.getX() - moveRoot);
+ child.setProperty(InternalProperties.EDGE_BEND_HEIGHT,
+ child.getProperty(InternalProperties.LEFT_OUTLINE).getAbsoluteY());
+ }
+
+ // set bendHeights for children right of the parent
+ int i = 0;
+ while (i < childrenSize - 1 && children.get(i).getX() + children.get(i).getWidth()
+ + children.get(i).getProperty(CoreOptions.MARGINS).right - graph.getWidth() / 2.0 <= 0.0) {
+ i++;
+ }
+
+ double globalBendHeight = children.get(i).getProperty(InternalProperties.EDGE_BEND_HEIGHT);
+ for (int a = 0; a < childrenSize; a++) {
+ if (globalBendHeight < children.get(a).getProperty(InternalProperties.EDGE_BEND_HEIGHT)) {
+ children.get(a).setProperty(InternalProperties.EDGE_BEND_HEIGHT, globalBendHeight);
+ } else {
+ globalBendHeight = children.get(a).getProperty(InternalProperties.EDGE_BEND_HEIGHT);
+ }
+ }
+
+ // set bendHeights for children left of the parent
+ i = childrenSize - 1;
+ while (i > 0 && children.get(i).getX() - children.get(i).getProperty(CoreOptions.MARGINS).left
+ - graph.getWidth() / 2.0 >= 0.0) {
+ i--;
+ }
+
+ if (i < childrenSize) {
+ for (int a = i; a >= 0; a--) {
+
+ if (globalBendHeight < children.get(a).getProperty(InternalProperties.EDGE_BEND_HEIGHT)) {
+ children.get(a).setProperty(InternalProperties.EDGE_BEND_HEIGHT, globalBendHeight);
+ } else {
+ globalBendHeight = children.get(a).getProperty(InternalProperties.EDGE_BEND_HEIGHT);
+ }
+ }
+ }
+
+ double newX;
+ OutlineNode newOutlinepart;
+ // left outline update
+ OutlineNode leftChildOutline = children.get(0).getProperty(InternalProperties.LEFT_OUTLINE);
+ OutlineNode graphLeftOutline = graph.getProperty(InternalProperties.LEFT_OUTLINE);
+
+ newX = children.get(0).getX() + leftChildOutline.getRelativeX()
+ - graphLeftOutline.getRelativeX();
+ newOutlinepart = new OutlineNode(0.0, leftChildOutline
+ .getAbsoluteY(), leftChildOutline.getNext());
+ graphLeftOutline.getNext().getNext().getNext()
+ .setNext(new OutlineNode(newX, children.get(0).getProperty(InternalProperties.EDGE_BEND_HEIGHT),
+ newOutlinepart));
+
+ // right outline update
+ OutlineNode graphRightOutline = graph.getProperty(InternalProperties.RIGHT_OUTLINE);
+ OutlineNode rightChildOutline = children.get(childrenSize - 1).getProperty(InternalProperties.RIGHT_OUTLINE);
+
+ newX = children.get(childrenSize - 1).getX() + rightChildOutline.getRelativeX() - graphRightOutline.getRelativeX();
+ newOutlinepart = new OutlineNode(0.0, rightChildOutline.getAbsoluteY(), rightChildOutline.getNext());
+ graphRightOutline.getNext().getNext().getNext().setNext(
+ new OutlineNode(
+ newX, children.get(childrenSize - 1).getProperty(InternalProperties.EDGE_BEND_HEIGHT), newOutlinepart
+ ));
+
+ // update outlineMaxY
+ // update min und max for x and y
+ for (ElkNode child: children) {
+ graph.setProperty(InternalProperties.OUTLINE_MAX_DEPTH, Math.max(graph.getProperty(InternalProperties
+ .OUTLINE_MAX_DEPTH), child.getProperty(InternalProperties.OUTLINE_MAX_DEPTH)));
+ graph.setProperty(InternalProperties.MIN_X, Math.min(graph.getProperty(InternalProperties.MIN_X),
+ child.getX() + child.getProperty(InternalProperties.MIN_X)));
+ graph.setProperty(InternalProperties.MAX_X, Math.max(graph.getProperty(InternalProperties.MAX_X),
+ child.getX() + child.getProperty(InternalProperties.MAX_X)));
+ }
+ graph.setProperty(InternalProperties.MAX_Y, graph.getProperty(InternalProperties.OUTLINE_MAX_DEPTH));
+
+ }
+
+ }
+
+ /** Sorts the subTrees in a semi-circle. */
+ private void sortSubTrees(final List children) {
+
+ // first, we sort the SubTrees by the Y-coordinate of their root.
+ Collections.sort(children, new NodeComparator(false));
+
+ List a = new ArrayList<>();
+ List b = new ArrayList<>();
+
+ if (considerNodeModelOrder) {
+ splitNodesWithModelOrder(children, a, b);
+ } else {
+ // now we need to put them in a V-shape
+ // the deepest element gets ignored in the calculation of the widths.
+ a.add(children.get(children.size() - 1));
+ double widthA = 0.0, widthB = 0.0;
+ for (int i = 1; i < children.size(); i++) {
+ if (widthA <= widthB) {
+ a.add(children.get(children.size() - 1 - i));
+ widthA += children.get(children.size() - 1 - i).getWidth();
+ } else {
+ b.add(children.get(children.size() - 1 - i));
+ widthB += children.get(children.size() - 1 - i).getWidth();
+ }
+ }
+ Collections.reverse(a);
+ }
+
+ Collections.sort(b, new NodeComparator(true));
+ a.addAll(b);
+ for (int i = 0; i < children.size(); i++) {
+ children.set(i, a.get(i));
+ }
+
+ }
+
+ /** Split nodes into two lists, while maintaining a sensible model order for nodes on the same height. */
+ private void splitNodesWithModelOrder(List original, List left, List right) {
+ // identify next subgroup (all at same y)
+ //split group in it in the weighted middle (always add first half to left group, and second half to right group)
+ if (original.size() == 0) {
+ return;
+ }
+ if (original.size() == 1) {
+ left.add(original.get(0));
+ }
+ if (original.size() == 2) {
+ // use model order to decide which node to put into which list regardless what the height ordering says
+ ElkNode first = original.get(0);
+ ElkNode second = original.get(1);
+
+ if (first.getProperty(InternalProperties.NODE_MODEL_ORDER)
+ > second.getProperty(InternalProperties.NODE_MODEL_ORDER)) {
+ left.add(second);
+ right.add(first);
+ } else {
+ left.add(first);
+ right.add(second);
+ }
+ }
+
+ List currentGroup = new ArrayList<>();
+ Pair widthLeftRight = new Pair<>();
+ widthLeftRight.setFirst(0.0);
+ widthLeftRight.setSecond(0.0);
+ ElkNode current = original.get(0);
+ currentGroup.add(current);
+ for (int i = 1; i < original.size(); i++) {
+ ElkNode next = original.get(i);
+ if (Double.compare(current.getY(), next.getY()) == 0) {
+ // add next node to group because it has the same height as the previous element
+ currentGroup.add(next);
+ } else {
+ // split and add entire group
+ int finalIndexOfLeft = splitGroup(currentGroup, widthLeftRight);
+ left.addAll(currentGroup.subList(0, finalIndexOfLeft + 1));
+ right.addAll(currentGroup.subList(finalIndexOfLeft + 1, currentGroup.size()));
+
+ // reset group
+ currentGroup = new ArrayList<>();
+ currentGroup.add(next);
+ }
+ current = next;
+
+ // if next element is last element split and add the current group now
+ if (i == original.size() - 1) {
+ int finalIndexOfLeft = splitGroup(currentGroup, widthLeftRight);
+ left.addAll(currentGroup.subList(0, finalIndexOfLeft + 1));
+ right.addAll(currentGroup.subList(finalIndexOfLeft + 1, currentGroup.size()));
+ }
+ }
+ }
+
+ /** Find a sensible splitting point to divide elements into a left and right list according to a given width of both
+ * lists.
+ * @param group the group to be split
+ * @param widthLeftRight the current widths of the left and right lists
+ * @return the index position at which to split the group
+ */
+ private int splitGroup(List group, Pair widthLeftRight) {
+ if (group.size() == 1) {
+ return 0;
+ } else {
+ double widthLeft = widthLeftRight.getFirst();
+ double widthRight = widthLeftRight.getSecond();
+
+ double totalNewWidth = 0;
+ for (ElkNode node : group) {
+ totalNewWidth += node.getWidth();
+ }
+
+ // linear equations to find desired widths to fill both lists so that they reach equal length
+ // I: a + x = b + y
+ // II: x + y = t
+ // where a and b are widthLeft and widthRight, x and y are the desired additions to left and right
+ // and t is the totalNewWidth
+ // solution: x = (t-a+b)/2, y = t - (t-a+b)/2
+
+ double desiredLeft = (totalNewWidth - widthLeft + widthRight) / 2;
+// double desiredRight = totalNewWidth - desiredLeft;
+
+ // add nodes to left side until desiredLeft is exceeded
+ // remove last element and compare both solutions, take the one which is closer to the desired solution
+ int i = 0;
+ double newLeftWidth = 0;
+ while (desiredLeft < newLeftWidth && i < group.size()) {
+ newLeftWidth += group.get(i).getWidth();
+ }
+
+ double exceed = newLeftWidth - desiredLeft;
+ double under = desiredLeft - (newLeftWidth - group.get(i).getWidth());
+
+ int resultIndex;
+ if (exceed > under) {
+ resultIndex = i;
+ } else {
+ resultIndex = i - 1;
+ }
+
+ // add new widths to accumulated widths
+ widthLeftRight.setFirst(widthLeft + exceed);
+ double newRightWidth = 0;
+ for (int j = resultIndex + 1; j < group.size(); j++) {
+ newRightWidth += group.get(j).getWidth();
+ }
+ widthLeftRight.setSecond(widthRight + newRightWidth);
+
+ return resultIndex;
+ }
+ }
+
+ /** Create the initial outlines around a node. */
+ private void makeSimpleOutlines(final ElkNode graph) {
+ ElkMargin margins = graph.getProperty(CoreOptions.MARGINS);
+
+ // set the properties for left and right outlines
+ OutlineNode endpart;
+ endpart = new OutlineNode(0.0, graph.getY() + graph.getHeight() + margins.bottom + spacingNodeNode / 2,
+ new OutlineNode(graph.getWidth() / 2.0,
+ graph.getY() + graph.getHeight() + margins.bottom + spacingNodeNode / 2, null));
+
+ graph.setProperty(InternalProperties.LEFT_OUTLINE, new OutlineNode((-margins.left - spacingNodeNode / 2)
+ + graph.getWidth() / 2.0, graph.getY() - margins.top - spacingNodeNode / 2,
+ new OutlineNode(-graph.getWidth() / 2.0, graph.getY() - margins.top, endpart)));
+
+ endpart = new OutlineNode(0.0, graph.getY() + graph.getHeight() + margins.bottom,
+ new OutlineNode(-graph.getWidth() / 2.0,
+ graph.getY() + graph.getHeight() + margins.bottom + spacingNodeNode / 2, null));
+
+ graph.setProperty(InternalProperties.RIGHT_OUTLINE, new OutlineNode(graph.getWidth() / 2.0
+ + margins.right + spacingNodeNode / 2, graph.getY() - margins.top,
+ new OutlineNode(graph.getWidth() / 2.0, graph.getY() - margins.top - spacingNodeNode / 2, endpart)));
+
+ // set min and max values
+ graph.setProperty(InternalProperties.MIN_X, graph.getX() - margins.left);
+ graph.setProperty(InternalProperties.MAX_X,
+ graph.getX() + margins.right + graph.getWidth());
+ graph.setProperty(InternalProperties.MIN_Y, graph.getY() - margins.top);
+ graph.setProperty(InternalProperties.MAX_Y,
+ graph.getY() + margins.bottom + graph.getHeight());
+ graph.setProperty(InternalProperties.OUTLINE_MAX_DEPTH, graph.getProperty(InternalProperties.LEFT_OUTLINE)
+ .getNext().getNext().getAbsoluteY());
+
+ }
+
+ /**
+ * Combines the individual outlines of several sibling nodes to form the outline of the tree that they belong to.
+ */
+ private void bundleChildren(final ElkNode leftSubtree, final ElkNode a, final ElkNode b) {
+
+ double deltaX, deltaY, change;
+
+ // calculate distance between the two parts
+ double dist = outlineDistance(a.getProperty(InternalProperties.RIGHT_OUTLINE),
+ b.getProperty(InternalProperties.LEFT_OUTLINE));
+ b.setX(a.getX() + dist);
+
+ // enhance the left outline
+ if (leftSubtree.getProperty(InternalProperties.OUTLINE_MAX_DEPTH)
+ < b.getProperty(InternalProperties.OUTLINE_MAX_DEPTH)) {
+ OutlineNode lastL = leftSubtree.getProperty(InternalProperties.LEFT_OUTLINE);
+ double lAbsX = lastL.getRelativeX() + leftSubtree.getX();
+
+ // move to the end of leftSubtree
+ while (!lastL.isLast()) {
+ lastL = lastL.getNext();
+ lAbsX += lastL.getRelativeX();
+ }
+ // find fitting position in the left outline of b
+ OutlineNode bItterator = new OutlineNode(b.getProperty(InternalProperties.LEFT_OUTLINE).getRelativeX(),
+ MINIMAL_Y, b.getProperty(InternalProperties.LEFT_OUTLINE).getNext());
+ double rAbsX = bItterator.getRelativeX() + b.getX();
+ while (bItterator.getNext().getAbsoluteY() <= lastL.getAbsoluteY()) {
+ bItterator = bItterator.getNext();
+ rAbsX += bItterator.getRelativeX();
+ }
+ // now we calculate the change
+ deltaX = bItterator.getNext().getRelativeX();
+ deltaY = bItterator.getNext().getAbsoluteY() - bItterator.getAbsoluteY();
+ change = ((lastL.getAbsoluteY() - bItterator.getAbsoluteY()) * deltaX) / deltaY;
+
+ // now we calculate the new points
+ double newX = -lAbsX + rAbsX + change;
+ OutlineNode newNext = new OutlineNode(bItterator.getNext().getRelativeX() - change, bItterator.getNext()
+ .getAbsoluteY(), bItterator.getNext().getNext());
+ lastL.setNext(new OutlineNode(newX, lastL.getAbsoluteY(), newNext));
+
+ // now update outline_max_depth
+ leftSubtree.setProperty(InternalProperties.OUTLINE_MAX_DEPTH,
+ b.getProperty(InternalProperties.OUTLINE_MAX_DEPTH));
+ }
+
+ // enhance the right outline
+ if (b.getProperty(InternalProperties.OUTLINE_MAX_DEPTH) < a.getProperty(InternalProperties.OUTLINE_MAX_DEPTH)) {
+ OutlineNode lastB = b.getProperty(InternalProperties.RIGHT_OUTLINE);
+ double rAbsX = lastB.getRelativeX() + b.getX();
+
+ // move to the end of b
+ while (!lastB.isLast()) {
+ lastB = lastB.getNext();
+ rAbsX += lastB.getRelativeX();
+ }
+ // find fitting position in the right outline of a
+ OutlineNode aItterator = new OutlineNode(a.getProperty(InternalProperties.RIGHT_OUTLINE).getRelativeX(),
+ MINIMAL_Y, a.getProperty(InternalProperties.RIGHT_OUTLINE).getNext());
+ double aAbsX = aItterator.getRelativeX() + a.getX();
+ while (aItterator.getNext().getAbsoluteY() <= lastB.getAbsoluteY()) {
+ aItterator = aItterator.getNext();
+ aAbsX += aItterator.getRelativeX();
+ }
+ // now we calculate the change
+ deltaX = aItterator.getNext().getRelativeX();
+ deltaY = aItterator.getNext().getAbsoluteY() - aItterator.getAbsoluteY();
+ change = ((lastB.getAbsoluteY() - aItterator.getAbsoluteY()) * deltaX) / deltaY;
+
+ // now we calculate the new points
+ double newX = aAbsX - rAbsX + change;
+ OutlineNode newNext = new OutlineNode(aItterator.getNext().getRelativeX() - change,
+ aItterator.getNext().getAbsoluteY(), aItterator.getNext().getNext());
+ lastB.setNext(new OutlineNode(newX, lastB.getAbsoluteY(), newNext));
+ // now update outline_max_depth
+ b.setProperty(InternalProperties.OUTLINE_MAX_DEPTH, a.getProperty(InternalProperties.OUTLINE_MAX_DEPTH));
+ }
+ }
+
+ @Override
+ public LayoutProcessorConfiguration
+ getLayoutProcessorConfiguration(final ElkNode graph) {
+ return null;
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacerStrategy.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacerStrategy.java
new file mode 100644
index 0000000000..20d0bb2276
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p2relative/RelativeXPlacerStrategy.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p2relative;
+
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.ILayoutPhaseFactory;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Horizontal node placement strategies.
+ *
+ */
+public enum RelativeXPlacerStrategy implements ILayoutPhaseFactory {
+
+ /**
+ * Simple strategy for setting the horizontal positions of nodes. These positions are relative to their parents
+ * and chosen such that overlaps are avoided.
+ */
+ SIMPLE_X_PLACING;
+
+ @Override
+ public ILayoutPhase create() {
+ switch (this) {
+ case SIMPLE_X_PLACING:
+ return new RelativeXPlacer();
+
+ default:
+ throw new IllegalArgumentException(
+ "No implementation is available for the node placer " + this.toString());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacer.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacer.java
new file mode 100644
index 0000000000..a80eacdfcf
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacer.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p3absolute;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Computes absolute x coordinates from the previously computed relative coordinates.
+ *
+ */
+public class AbsoluteXPlacer implements ILayoutPhase {
+
+ @Override
+ public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+ progressMonitor.begin("AbsolutPlacer", 1);
+
+ if (!graph.getChildren().isEmpty()) {
+ ElkNode parent = graph.getProperty(InternalProperties.ROOT_NODE);
+
+ // first, move the root
+ parent.setX(parent.getX() - findMinimalX(parent));
+ // now we update the whole tree to absolute X
+ absoluteTreeCoords(parent);
+ }
+
+ progressMonitor.done();
+ }
+
+ /** Find leftmost subtree. */
+ private double findMinimalX(final ElkNode tree) {
+ int numOfChildren = tree.getOutgoingEdges().size();
+ if (numOfChildren == 0) {
+ return tree.getX();
+ } else {
+ double minSubtreeX = 0.0;
+ double testX = 0.0;
+ for (int i = 0; i < numOfChildren; i++) {
+ testX = findMinimalX((ElkNode) tree.getOutgoingEdges().get(i).getTargets().get(0));
+ minSubtreeX = (testX < minSubtreeX) ? testX : minSubtreeX;
+ }
+ return minSubtreeX + tree.getX();
+ }
+ }
+
+ private void absoluteTreeCoords(final ElkNode tree) {
+ int numOfChildren = tree.getOutgoingEdges().size();
+ if (numOfChildren > 0) {
+ ElkNode child;
+ for (int i = 0; i < numOfChildren; i++) {
+ child = (ElkNode) tree.getOutgoingEdges().get(i).getTargets().get(0);
+ child.setX(child.getX() + tree.getX());
+ absoluteTreeCoords(child);
+ }
+ }
+ }
+
+
+ @Override
+ public LayoutProcessorConfiguration getLayoutProcessorConfiguration(
+ final ElkNode graph) {
+ return null;
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacerStrategy.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacerStrategy.java
new file mode 100644
index 0000000000..a837274387
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p3absolute/AbsoluteXPlacerStrategy.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p3absolute;
+
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.ILayoutPhaseFactory;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Strategies for absolute node placement.
+ *
+ */
+public enum AbsoluteXPlacerStrategy implements ILayoutPhaseFactory {
+
+ /**
+ * Compute absolute x-coordinates based on relative coordinates
+ */
+ ABSOLUTE_XPLACING;
+
+ @Override
+ public ILayoutPhase create() {
+ switch (this) {
+ case ABSOLUTE_XPLACING:
+ return new AbsoluteXPlacer();
+
+ default:
+ throw new IllegalArgumentException(
+ "No implementation is available for the node placer " + this.toString());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/BendEdgeRouter.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/BendEdgeRouter.java
new file mode 100644
index 0000000000..2083e4900f
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/BendEdgeRouter.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p4edgerouting;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkEdge;
+import org.eclipse.elk.graph.ElkEdgeSection;
+import org.eclipse.elk.graph.ElkNode;
+import org.eclipse.elk.graph.util.ElkGraphUtil;
+
+/**
+ * Implements edge routing that includes bendpoints when straight edges would cross nodes.
+ *
+ */
+public class BendEdgeRouter implements ILayoutPhase {
+
+ @Override
+ public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+
+ progressMonitor.begin("BendEdgeRouter", 1);
+
+ if (!graph.getChildren().isEmpty()) {
+ ElkNode parent = graph.getProperty(InternalProperties.ROOT_NODE);
+
+ routeEdges(parent);
+ }
+
+ progressMonitor.done();
+
+ }
+
+ @Override
+ public LayoutProcessorConfiguration getLayoutProcessorConfiguration(
+ final ElkNode graph) {
+ return null;
+ }
+
+ /** Route the edges with bendpoints. */
+ private void routeEdges(final ElkNode node) {
+ for (ElkEdge edge : ElkGraphUtil.allOutgoingEdges(node)) {
+ ElkNode target = ElkGraphUtil.connectableShapeToNode(edge.getTargets().get(0));
+ ElkEdgeSection section = ElkGraphUtil.firstEdgeSection(edge, true, true);
+
+
+ double startX = node.getX() + node.getWidth() / 2;
+ double startY = node.getY() + node.getHeight();
+ double endX = target.getX() + target.getWidth() / 2;
+ double endY = target.getY();
+
+ section.setStartLocation(startX, startY);
+ section.setEndLocation(endX, endY);
+
+ double bendheight = target.getProperty(InternalProperties.EDGE_BEND_HEIGHT);
+ double epsilon = 0.0001;
+ // if the node is low place a bendpoint above it
+ if (Math.abs(bendheight
+ - (endY - target.getParent().getProperty(CoreOptions.SPACING_NODE_NODE) / 2)) > epsilon) {
+ ElkGraphUtil.createBendPoint(section, endX, bendheight);
+ }
+ routeEdges(target);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/EdgerouterStrategy.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/EdgerouterStrategy.java
new file mode 100644
index 0000000000..c275d14d0d
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/EdgerouterStrategy.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p4edgerouting;
+
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.ILayoutPhaseFactory;
+import org.eclipse.elk.graph.ElkNode;
+
+/**
+ * Edge routing strategies.
+ *
+ */
+public enum EdgerouterStrategy implements ILayoutPhaseFactory {
+
+ /**
+ * Straight edge routing.
+ */
+ DIRECT_ROUTING,
+ /**
+ * Routing with bendpoints if nodes are in the way.
+ */
+ BEND_ROUTING;
+
+ @Override
+ public ILayoutPhase create() {
+ switch (this) {
+ case DIRECT_ROUTING:
+ return new StraightEdgeRouter();
+ case BEND_ROUTING:
+ return new BendEdgeRouter();
+ default:
+ throw new IllegalArgumentException(
+ "No implementation is available for the edge router " + this.toString());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/StraightEdgeRouter.java b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/StraightEdgeRouter.java
new file mode 100644
index 0000000000..4187b4b64e
--- /dev/null
+++ b/plugins/org.eclipse.elk.alg.vertiflex/src/org/eclipse/elk/alg/vertiflex/p4edgerouting/StraightEdgeRouter.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Kiel University and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.elk.alg.vertiflex.p4edgerouting;
+
+import org.eclipse.elk.alg.vertiflex.InternalProperties;
+import org.eclipse.elk.alg.vertiflex.VertiFlexLayoutPhases;
+import org.eclipse.elk.core.alg.ILayoutPhase;
+import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkEdge;
+import org.eclipse.elk.graph.ElkEdgeSection;
+import org.eclipse.elk.graph.ElkNode;
+import org.eclipse.elk.graph.util.ElkGraphUtil;
+
+/**
+ * An edge router that draws straight edges between nodes.
+ *
+ */
+public class StraightEdgeRouter implements ILayoutPhase {
+
+ private IElkProgressMonitor myProgressMonitor;
+
+
+ @Override
+ public void process(final ElkNode graph, final IElkProgressMonitor progressMonitor) {
+ myProgressMonitor = progressMonitor;
+ myProgressMonitor.begin("StraightEdgeRouter", 1);
+
+ if (!graph.getChildren().isEmpty()) {
+ ElkNode parent = graph.getProperty(InternalProperties.ROOT_NODE);
+
+ routeEdges(parent);
+ }
+
+ myProgressMonitor.done();
+ }
+
+ @Override
+ public LayoutProcessorConfiguration getLayoutProcessorConfiguration(
+ final ElkNode graph) {
+ return null;
+ }
+
+
+ /** Route the edges with with straight edges. */
+ private void routeEdges(final ElkNode node) {
+ for (ElkEdge edge : ElkGraphUtil.allOutgoingEdges(node)) {
+ ElkNode target = ElkGraphUtil.connectableShapeToNode(edge.getTargets().get(0));
+ ElkEdgeSection section = ElkGraphUtil.firstEdgeSection(edge, true, true);
+
+
+ double startX = node.getX() + node.getWidth() / 2;
+ double startY = node.getY() + node.getHeight();
+ double endX = target.getX() + target.getWidth() / 2;
+ double endY = target.getY();
+
+ section.setStartLocation(startX, startY);
+ section.setEndLocation(endX, endY);
+ routeEdges(target);
+ }
+ }
+}
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 2dd26f55bb..30b228ddc7 100644
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -37,6 +37,7 @@
org.eclipse.elk.alg.rectpacking
org.eclipse.elk.alg.spore
org.eclipse.elk.alg.topdownpacking
+ org.eclipse.elk.alg.vertiflex
org.eclipse.elk.conn.gmf
org.eclipse.elk.core
org.eclipse.elk.core.debug
diff --git a/test/org.eclipse.elk.alg.test/META-INF/MANIFEST.MF b/test/org.eclipse.elk.alg.test/META-INF/MANIFEST.MF
index f8ca795720..a26fe56c2f 100644
--- a/test/org.eclipse.elk.alg.test/META-INF/MANIFEST.MF
+++ b/test/org.eclipse.elk.alg.test/META-INF/MANIFEST.MF
@@ -17,6 +17,7 @@ Require-Bundle: com.google.guava,
org.eclipse.elk.alg.radial,
org.eclipse.elk.alg.rectpacking,
org.eclipse.elk.alg.spore,
+ org.eclipse.elk.alg.vertiflex,
org.junit;bundle-version="4.12.0",
org.eclipse.swt;bundle-version="3.107.0",
org.eclipse.xtext;bundle-version="2.12.0",
diff --git a/test/org.eclipse.elk.alg.test/src/org/eclipse/elk/alg/test/PlainJavaInitialization.java b/test/org.eclipse.elk.alg.test/src/org/eclipse/elk/alg/test/PlainJavaInitialization.java
index 92c1b3f4b8..87b8e648c9 100644
--- a/test/org.eclipse.elk.alg.test/src/org/eclipse/elk/alg/test/PlainJavaInitialization.java
+++ b/test/org.eclipse.elk.alg.test/src/org/eclipse/elk/alg/test/PlainJavaInitialization.java
@@ -17,6 +17,7 @@
import org.eclipse.elk.alg.radial.options.RadialMetaDataProvider;
import org.eclipse.elk.alg.rectpacking.options.RectPackingMetaDataProvider;
import org.eclipse.elk.alg.spore.options.SporeMetaDataProvider;
+import org.eclipse.elk.alg.vertiflex.options.VertiFlexMetaDataProvider;
import org.eclipse.elk.core.data.ILayoutMetaDataProvider;
import org.eclipse.elk.core.data.LayoutMetaDataService;
import org.eclipse.elk.core.debug.grandom.GRandomStandaloneSetup;
@@ -42,7 +43,8 @@ public final class PlainJavaInitialization {
new RadialMetaDataProvider(),
new RectPackingMetaDataProvider(),
new SporeMetaDataProvider(),
- new StressMetaDataProvider()
+ new StressMetaDataProvider(),
+ new VertiFlexMetaDataProvider()
};
/**