Browse Source

Patched several things to make analyze_LoLA work as notebook

Yentl Van Tendeloo 3 years ago
parent
commit
7f43973241
5 changed files with 211 additions and 181 deletions
  1. 149 159
      bootstrap/core_algorithm.alc
  2. 6 2
      examples/analyze_LoLa.py
  3. 19 0
      models/PNPath/metamodels/PNPath.mvc
  4. 18 8
      notebook.ipynb
  5. 19 12
      wrappers/modelverse.py

+ 149 - 159
bootstrap/core_algorithm.alc

@@ -295,7 +295,7 @@ String function get_group_id(name : String):
 	
 	return ""!
 
-String function create_folders(user_id : String, folder_name : String):
+String function create_folders(user_id : String, folder_name : String, permissions : Boolean):
 	Element hierarchy
 	Integer i
 	String prev
@@ -323,7 +323,7 @@ String function create_folders(user_id : String, folder_name : String):
 			cummul = string_join(cummul + "/", elem)
 
 		if (get_entry_id(cummul) == ""):
-			if (allow_write(current_user_id, prev)):
+			if (bool_or(permissions, allow_write(current_user_id, prev))):
 				// Element does not exist yet!
 				new_entry = instantiate_node(core, "Folder", "")
 				instantiate_attribute(core, new_entry, "name", cummul)
@@ -332,6 +332,7 @@ String function create_folders(user_id : String, folder_name : String):
 				instantiate_link(core, "group", "", new_entry, get_group_id("nobody"))
 				instantiate_link(core, "owner", "", new_entry, user_id)
 			else:
+				log("Not allowed to write to " + cast_string(read_attribute(core, prev, "name")))
 				return read_root()!
 
 		prev = get_entry_id(cummul)
@@ -348,7 +349,7 @@ String function store_entry(model_id : String, full_name : String, user_id : Str
 		// Delete any old models
 		model_delete(get_entry_id(full_name))
 
-	prev = create_folders(user_id, get_foldername(full_name))
+	prev = create_folders(user_id, get_foldername(full_name), True)
 	instantiate_link(core, "contains", "", prev, model_id)
 	return full_name!
 
@@ -375,25 +376,28 @@ String function export_typing(model : Element, name : String):
 	return result!
 
 Void function model_create(model : Element, name : String, type_id : String, kind : String):
-	String location
-	String model_id
-	String instance_of
+	if (get_entry_id(name) != ""):
+		model_overwrite(model, get_entry_id(name), type_id)
+	else:
+		String location
+		String model_id
+		String instance_of
 
-	// Create model itself
-	location = "models/" + cast_id(model)
-	export_node(location, model["model"])
+		// Create model itself
+		location = "models/" + cast_id(model)
+		export_node(location, model["model"])
 
-	model_id = instantiate_node(core, kind, "")
-	instantiate_attribute(core, model_id, "name", store_entry(model_id, name, current_user_id))
-	instantiate_attribute(core, model_id, "location", location)
-	instantiate_attribute(core, model_id, "permissions", "200")
-	instantiate_link(core, "owner", "", model_id, current_user_id)
-	instantiate_link(core, "group", "", model_id, get_group_id("nobody"))
-	instance_of = instantiate_link(core, "instanceOf", "", model_id, type_id)
-	//instantiate_link(core, "semantics", "", instance_of, get_entry_id("models/conformance_mv"))
+		model_id = instantiate_node(core, kind, "")
+		instantiate_attribute(core, model_id, "name", store_entry(model_id, name, current_user_id))
+		instantiate_attribute(core, model_id, "location", location)
+		instantiate_attribute(core, model_id, "permissions", "200")
+		instantiate_link(core, "owner", "", model_id, current_user_id)
+		instantiate_link(core, "group", "", model_id, get_group_id("nobody"))
+		instance_of = instantiate_link(core, "instanceOf", "", model_id, type_id)
+		//instantiate_link(core, "semantics", "", instance_of, get_entry_id("models/conformance_mv"))
 
-	// Create type mapping model
-	instantiate_link(core, "typing", "", instance_of, export_typing(model, name))
+		// Create type mapping model
+		instantiate_link(core, "typing", "", instance_of, export_typing(model, name))
 
 	return!
 
@@ -522,11 +526,7 @@ Element function get_model(model_name : String, metamodel_name : String):
 
 Void function store_model(model_name : String, metamodel_name : String, model : Element):
 	core = import_node(core_model_location)
-	if (get_entry_id(model_name) == ""):
-		// New model
-		model_create(model, model_name, get_entry_id(metamodel_name), "Model")
-	else:
-		model_overwrite(model, get_entry_id(model_name), get_entry_id(metamodel_name))
+	model_create(model, model_name, get_entry_id(metamodel_name), "Model")
 	return!
 
 String function get_ramified_metamodel(model_name : String):
@@ -808,11 +808,7 @@ Boolean function enact_action(pm : Element, element : String, mapping : Element)
 		keys = dict_keys(outputs)
 		while (set_len(keys) > 0):
 			key = set_pop(keys)
-			if (get_entry_id(output_map[key]) == ""):
-				// New model
-				model_create(result[key], output_map[key], get_entry_id(outputs[key]), "Model")
-			else:
-				model_overwrite(result[key], get_entry_id(output_map[key]), get_entry_id(outputs[key]))
+			model_create(result[key], output_map[key], get_entry_id(outputs[key]), "Model")
 		output("Success")
 		return True!
 
@@ -1063,32 +1059,29 @@ String function cmd_model_move(source : String, target : String):
 	source_id = get_entry_id(source)
 	if (source_id != ""):
 		if (allow_write(current_user_id, source_id)):
-			if (get_entry_id(target) == ""):
-				// Create folders on path to the target
-				if (element_neq(create_folders(current_user_id, get_foldername(target)), read_root())):
-					if (allow_write(current_user_id, get_entry_id(get_foldername(target)))):
-						// Change location, first the name
-						instantiate_attribute(core, source_id, "name", target)
-
-						// Now the folder links
-						Element links
-						links = allIncomingAssociationInstances(core, source_id, "contains")
-						while (set_len(links) > 0):
-							model_delete_element(core, set_pop(links))
-						instantiate_link(core, "contains", "", get_entry_id(get_foldername(target)), source_id)
-
-						// Flush caches
-						dict_add(caches["models"], target, caches["models"][source])
-						dict_delete(caches["models"], source)
-
-						// Done
-						return "Success"!
-					else:
-						return "Write permission denied to: " + target!
+			// Create folders on path to the target
+			if (element_neq(create_folders(current_user_id, get_foldername(target), False), read_root())):
+				if (allow_write(current_user_id, get_entry_id(get_foldername(target)))):
+					// Change location, first the name
+					instantiate_attribute(core, source_id, "name", target)
+
+					// Now the folder links
+					Element links
+					links = allIncomingAssociationInstances(core, source_id, "contains")
+					while (set_len(links) > 0):
+						model_delete_element(core, set_pop(links))
+					instantiate_link(core, "contains", "", get_entry_id(get_foldername(target)), source_id)
+
+					// Flush caches
+					dict_add(caches["models"], target, caches["models"][source])
+					dict_delete(caches["models"], source)
+
+					// Done
+					return "Success"!
 				else:
 					return "Write permission denied to: " + target!
 			else:
-				return "Model exists: " + target!
+				return "Write permission denied to: " + target!
 		else:
 			return "Write permission denied to: " + source!
 	else:
@@ -1110,19 +1103,16 @@ String function cmd_model_add(type : String, name : String, code : String):
 				// And is readable
 				mm = get_full_model(type_id, get_entry_id("formalisms/SimpleClassDiagrams"))
 				if (element_neq(mm, read_root())):
-					if (element_neq(create_folders(current_user_id, get_foldername(name)), read_root())):
+					if (element_neq(create_folders(current_user_id, get_foldername(name), False), read_root())):
 						if (allow_write(current_user_id, get_entry_id(get_foldername(name)))):
-							if (get_entry_id(name) == ""):
-								// Model doesn't exist yet
-								new_model = compile_model(code, mm)
+							new_model = compile_model(code, mm)
 
-								if (is_physical_string(new_model)):
-									return "Compilation error: " + cast_string(new_model)!
+							if (is_physical_string(new_model)):
+								return "Compilation error: " + cast_string(new_model)!
 
-								model_create(new_model, name, type_id, "Model")
-								return "Success"!
-							else:
-								return "Model exists: " + name!
+							model_create(new_model, name, type_id, "Model")
+
+							return "Success"!
 						else:
 							return "Write permission denied to: " + name!
 					else:
@@ -1913,64 +1903,64 @@ String function transformation_add(source_models : Element, target_models : Elem
 		else:
 			return "Model not found: " + name!
 
-	if (get_entry_id(operation_name) == ""):
-		// Write out a merged metamodel containing all these models: this is the MM for the manual operation
-		// New location is available, so write
-		if (dict_len(source_models) + dict_len(target_models) > 0):
-			merged_formalism = model_fuse(formalism_map)
-			model_create(merged_formalism, "merged/" + operation_name, get_entry_id("formalisms/SimpleClassDiagrams"), "Model")
-			do_spawn_modify("merged/" + operation_name, True)
-			merged_formalism = get_full_model(get_entry_id("merged/" + operation_name), get_entry_id("formalisms/SimpleClassDiagrams"))
-
-		if (operation_type == "manual"):
-			// Finished with all information, now create the model itself!
-			Element m
-			m = get_full_model(get_entry_id("formalisms/ManualOperation"), get_entry_id("formalisms/SimpleClassDiagrams"))
-			model_create(instantiate_model(m), operation_name, get_entry_id("formalisms/ManualOperation"), "ManualOperation")
-			model_id = get_entry_id(operation_name)
-
-		elif (operation_type == "actionlanguage"):
-			// Finished with all information, now create the model itself!
-			output("Waiting for code constructors...")
-			Element compiled
-			compiled = compile_code(input())
-			if (is_physical_string(compiled)):
-				return "Compilation error: " + cast_string(compiled)!
-
-			model_create(compiled, operation_name, get_entry_id("formalisms/ActionLanguage"), "ActionLanguage")
-			model_id = get_entry_id(operation_name)
-
-		if (dict_len(source_models) + dict_len(target_models) > 0):
-			merged_formalism_id = get_entry_id("merged/" + operation_name)
-
-			// Add tracability links at this level
-			while (set_len(all_formalisms) > 0):
-				source_formalism_id = set_pop(all_formalisms)
-				tracability_link = instantiate_link(core, "tracability", "", merged_formalism_id, source_formalism_id)
-				instantiate_attribute(core, tracability_link, "type", "merged")
-
-			tracability_link = instantiate_link(core, "tracability", "", model_id, merged_formalism_id)
-			instantiate_attribute(core, tracability_link, "type", "operatesOn")
-
-			// Extend metadata with info on source and target
-			String link
-			String dst
-
-			keys = dict_keys(source)
-			while (set_len(keys) > 0):
-				key = set_pop(keys)
-				link = instantiate_link(core, "transformInput", "", model_id, source[key])
-				instantiate_attribute(core, link, "name", key)
-
-			keys = dict_keys(target)
-			while (set_len(keys) > 0):
-				key = set_pop(keys)
-				link = instantiate_link(core, "transformOutput", "", model_id, target[key])
-				instantiate_attribute(core, link, "name", key)
+	if (get_entry_id(operation_name) != ""):
+		model_delete(get_entry_id(operation_name))
+
+	// Write out a merged metamodel containing all these models: this is the MM for the manual operation
+	// New location is available, so write
+	if (dict_len(source_models) + dict_len(target_models) > 0):
+		merged_formalism = model_fuse(formalism_map)
+		model_create(merged_formalism, "merged/" + operation_name, get_entry_id("formalisms/SimpleClassDiagrams"), "Model")
+		do_spawn_modify("merged/" + operation_name, True)
+		merged_formalism = get_full_model(get_entry_id("merged/" + operation_name), get_entry_id("formalisms/SimpleClassDiagrams"))
+
+	if (operation_type == "manual"):
+		// Finished with all information, now create the model itself!
+		Element m
+		m = get_full_model(get_entry_id("formalisms/ManualOperation"), get_entry_id("formalisms/SimpleClassDiagrams"))
+		model_create(instantiate_model(m), operation_name, get_entry_id("formalisms/ManualOperation"), "ManualOperation")
+		model_id = get_entry_id(operation_name)
+
+	elif (operation_type == "actionlanguage"):
+		// Finished with all information, now create the model itself!
+		output("Waiting for code constructors...")
+		Element compiled
+		compiled = compile_code(input())
+		if (is_physical_string(compiled)):
+			return "Compilation error: " + cast_string(compiled)!
+
+		model_create(compiled, operation_name, get_entry_id("formalisms/ActionLanguage"), "ActionLanguage")
+		model_id = get_entry_id(operation_name)
+
+	if (dict_len(source_models) + dict_len(target_models) > 0):
+		merged_formalism_id = get_entry_id("merged/" + operation_name)
+
+		// Add tracability links at this level
+		while (set_len(all_formalisms) > 0):
+			source_formalism_id = set_pop(all_formalisms)
+			tracability_link = instantiate_link(core, "tracability", "", merged_formalism_id, source_formalism_id)
+			instantiate_attribute(core, tracability_link, "type", "merged")
+
+		tracability_link = instantiate_link(core, "tracability", "", model_id, merged_formalism_id)
+		instantiate_attribute(core, tracability_link, "type", "operatesOn")
+
+		// Extend metadata with info on source and target
+		String link
+		String dst
+
+		keys = dict_keys(source)
+		while (set_len(keys) > 0):
+			key = set_pop(keys)
+			link = instantiate_link(core, "transformInput", "", model_id, source[key])
+			instantiate_attribute(core, link, "name", key)
+
+		keys = dict_keys(target)
+		while (set_len(keys) > 0):
+			key = set_pop(keys)
+			link = instantiate_link(core, "transformOutput", "", model_id, target[key])
+			instantiate_attribute(core, link, "name", key)
 			
 		return "Success"!
-	else:
-		return "Model exists: " + operation_name!
 
 String function cmd_transformation_add_MT(source_models : Element, target_models : Element, operation_name : String):
 	// Add a model transformation model
@@ -2071,50 +2061,50 @@ String function cmd_transformation_add_MT(source_models : Element, target_models
 	ramified_metamodel_id = get_entry_id("RAMified/" + operation_name)
 	
 	// Now use the RAMified model to create the instance
-	if (get_entry_id(operation_name) == ""):
-		String new_model
-		// Finished with all information, now create the model itself!
-		output("Waiting for model constructors...")
-		new_model = compile_model(input(), get_full_model(ramified_metamodel_id, get_entry_id("formalisms/SimpleClassDiagrams")))
-		if (is_physical_string(new_model)):
-			return "Compilation error: " + cast_string(new_model)!
-		model_create(new_model, operation_name, ramified_metamodel_id, "ModelTransformation")
-		model_id = get_entry_id(operation_name)
-
-		// Write out a merged metamodel containing all these models: this is the MM for the manual operation
-		// New location is available, so write
-		model_create(merged_formalism, "merged/" + operation_name, get_entry_id("formalisms/SimpleClassDiagrams"), "Model")
-		merged_formalism_id = get_entry_id("merged/" + operation_name)
+	if (get_entry_id(operation_name) != ""):
+		model_delete(get_entry_id(operation_name))
+
+	String new_model
+	// Finished with all information, now create the model itself!
+	output("Waiting for model constructors...")
+	new_model = compile_model(input(), get_full_model(ramified_metamodel_id, get_entry_id("formalisms/SimpleClassDiagrams")))
+	if (is_physical_string(new_model)):
+		return "Compilation error: " + cast_string(new_model)!
+	model_create(new_model, operation_name, ramified_metamodel_id, "ModelTransformation")
+	model_id = get_entry_id(operation_name)
+
+	// Write out a merged metamodel containing all these models: this is the MM for the manual operation
+	// New location is available, so write
+	model_create(merged_formalism, "merged/" + operation_name, get_entry_id("formalisms/SimpleClassDiagrams"), "Model")
+	merged_formalism_id = get_entry_id("merged/" + operation_name)
 
-		// Add tracability links at this level
-		tracability_link = instantiate_link(core, "tracability", "", model_id, merged_formalism_id)
-		instantiate_attribute(core, tracability_link, "type", "operatesOn")
+	// Add tracability links at this level
+	tracability_link = instantiate_link(core, "tracability", "", model_id, merged_formalism_id)
+	instantiate_attribute(core, tracability_link, "type", "operatesOn")
 
-		// Add tracability links at this level
-		tracability_link = instantiate_link(core, "tracability", "", merged_formalism_id, ramified_metamodel_id)
-		instantiate_attribute(core, tracability_link, "type", "RAMified")
+	// Add tracability links at this level
+	tracability_link = instantiate_link(core, "tracability", "", merged_formalism_id, ramified_metamodel_id)
+	instantiate_attribute(core, tracability_link, "type", "RAMified")
 
-		// Extend metadata with info on source and target
-		String link
-		String dst
+	// Extend metadata with info on source and target
+	String link
+	String dst
 
-		keys = dict_keys(source)
-		while (set_len(keys) > 0):
-			key = set_pop(keys)
-			dst = source[key]
-			link = instantiate_link(core, "transformInput", "", model_id, dst)
-			instantiate_attribute(core, link, "name", key)
+	keys = dict_keys(source)
+	while (set_len(keys) > 0):
+		key = set_pop(keys)
+		dst = source[key]
+		link = instantiate_link(core, "transformInput", "", model_id, dst)
+		instantiate_attribute(core, link, "name", key)
 
-		keys = dict_keys(target)
-		while (set_len(keys) > 0):
-			key = set_pop(keys)
-			dst = target[key]
-			link = instantiate_link(core, "transformOutput", "", model_id, dst)
-			instantiate_attribute(core, link, "name", key)
+	keys = dict_keys(target)
+	while (set_len(keys) > 0):
+		key = set_pop(keys)
+		dst = target[key]
+		link = instantiate_link(core, "transformOutput", "", model_id, dst)
+		instantiate_attribute(core, link, "name", key)
 
-		return "Success"!
-	else:
-		return "Model exists: " + operation_name!
+	return "Success"!
 
 String function cmd_permission_modify(model_name : String, permissions : String):
 	Integer permission
@@ -2389,7 +2379,7 @@ String function cmd_service_register(service_name : String):
 		// Service terminated
 		return "Success"!
 	else:
-		return "Service already exists: " + service_name!
+		return "Service exists: " + service_name!
 
 String function cmd_user_password(user_name : String, new_password : String):
 	if (bool_or(current_user_id == get_user_id(user_name), is_admin(current_user_id))):
@@ -2456,7 +2446,7 @@ String function cmd_model_types(model_name : String):
 	
 String function cmd_folder_create(folder_name : String):
 	if (get_entry_id(folder_name) == ""):
-		if (element_neq(create_folders(current_user_id, folder_name), read_root())):
+		if (element_neq(create_folders(current_user_id, folder_name, False), read_root())):
 			return "Success"!
 		else:
 			return "Write permission denied to: " + folder_name!
@@ -2568,7 +2558,7 @@ Void function new_task():
 
 				// Now create a folder for this user's models!
 				current_user_id = get_user_id("admin")
-				create_folders(user_id, "users/" + username)
+				create_folders(user_id, "users/" + username, False)
 				current_user_id = user_id
 
 				// Can continue as we are logged on now!

+ 6 - 2
examples/analyze_LoLa.py

@@ -7,10 +7,14 @@ login("admin", "admin")
 
 model_add("formalisms/PetriNets", "formalisms/SimpleClassDiagrams", open("models/PetriNets/metamodels/PetriNets.mvc", 'r').read())
 model_add("formalisms/Query", "formalisms/SimpleClassDiagrams", open("models/SafetyQuery/metamodels/query.mvc", 'r').read())
+model_add("formalisms/PNPath", "formalisms/SimpleClassDiagrams", open("models/PNPath/metamodels/PNPath.mvc", 'r').read())
 
 model_add("models/PN", "formalisms/PetriNets", open("models/PetriNets/models/critical_section_with_check.mvc", 'r').read())
 model_add("models/Query", "formalisms/Query", open("models/SafetyQuery/models/both_criticals_enabled.mvc", 'r').read())
 
-transformation_add_AL({"PN": "formalisms/PetriNets", "Query": "formalisms/Query"}, {}, "models/analyze_lola", open("models/PetriNets/transformations/analyze_lola.alc", 'r').read())
+transformation_add_AL({"PN": "formalisms/PetriNets", "Query": "formalisms/Query"}, {"Path": "formalisms/PNPath"}, "models/analyze_lola", open("models/PetriNets/transformations/analyze_lola.alc", 'r').read())
 
-transformation_execute_AL("models/analyze_lola", {"PN": "models/PN", "Query": "models/Query"}, {})
+if transformation_execute_AL("models/analyze_lola", {"PN": "models/PN", "Query": "models/Query"}, {"Path": "models/Path"}):
+    print("Not reachable!")
+else:
+    print("Reachable: " + str(element_list_nice("models/Path")))

+ 19 - 0
models/PNPath/metamodels/PNPath.mvc

@@ -0,0 +1,19 @@
+SimpleAttribute String {
+    name = "String"
+}
+
+Class Entry {
+    name = "Entry"
+    name : String
+}
+
+Class First : Entry {
+    name = "First"
+    lower_cardinality = 1
+    upper_cardinality = 1
+}
+
+Association Next (Entry, Entry){
+    source_upper_cardinality = 1
+    target_upper_cardinality = 1
+}

+ 18 - 8
notebook.ipynb

@@ -3,7 +3,9 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "import sys\n",
@@ -21,7 +23,9 @@
   {
    "cell_type": "code",
    "execution_count": 2,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "model_define(\"~/formalisms/PetriNets\", \"formalisms/SimpleClassDiagrams\", \"\"\"\n",
@@ -97,7 +101,9 @@
   {
    "cell_type": "code",
    "execution_count": 10,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "attr_assign(\"~/formalisms/PetriNets\", \"Place\", \"name\", \"Place\")\n",
@@ -107,14 +113,18 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": []
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": []
   }
@@ -128,14 +138,14 @@
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 3
+    "version": 2
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.5.5"
+   "pygments_lexer": "ipython2",
+   "version": "2.7.14"
   }
  },
  "nbformat": 4,

+ 19 - 12
wrappers/modelverse.py

@@ -287,6 +287,12 @@ def init(address_param="127.0.0.1:8001", timeout=60.0, taskname=None):
     return
 
 def login(username, password):
+    if username == None:
+        import uuid
+        username = str(uuid.uuid4())
+    if password == None:
+        import uuid
+        password = str(uuid.uuid4())
     controller.username = username
     controller.password = password
     INPUT("login", [username, password])
@@ -304,14 +310,6 @@ def model_add(model_name, metamodel_name, model_code=""):
     INPUT("model_add", [model_name, metamodel_name, model_code])
     return OUTPUT()
 
-def model_define(model_name, metamodel_name, model_code=""):
-    try:
-        INPUT("model_add", [model_name, metamodel_name, model_code])
-        return OUTPUT()
-    except ModelExists:
-        INPUT("model_overwrite", [model_name, model_code])
-        return OUTPUT()
-
 def model_move(source_name, target_name):
     INPUT("model_move", [source_name, target_name])
     return OUTPUT()
@@ -694,6 +692,7 @@ def show(model_name):
     past_y = 3
     max_x = 0
     max_y = 0
+    spacing = 50
 
     if is_scd:
         for elem in result:
@@ -772,19 +771,27 @@ def show(model_name):
             edges.append({"sx": x, "sy": y_loc_edge, "tx": x + max_text * 9 + 10, "ty": y_loc_edge})
             edges.append({"sx": x, "sy": y + 20, "tx": x + max_text * 9 + 10, "ty": y + 20})
 
-            past_x = x + max_text * 9 + 30
+            past_x = x + max_text * 9 + spacing
             if past_x > 1000:
                 max_x = max(max_x, past_x)
-                past_x = 0
-                past_y = max_y + 30
+                past_x = 3
+                past_y = max_y + spacing
 
             max_x = max(max_x, past_x)
             max_y = max(max_y, past_y + text_skip * 3 + text_counter * text_skip)
 
     data_content = '<svg width="%s" height="%s">' % (max_x + 3, max_y + 3)
+    data_content += '<marker orient="auto" refY="0.0" refX="0.0" id="triangle" style="overflow:visible;"><path id="path941" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" transform="scale(0.8) rotate(180) translate(12.5,0)"/></marker>'
 
     for edge in todo_edges:
-        data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black"/>' % (locations[edge["source"]][0] + edge["source_x"], locations[edge["source"]][1] + edge["source_y"], locations[edge["target"]][0] + edge["target_x"], locations[edge["target"]][1] + edge["target_y"])
+        sx = locations[edge["source"]][0] + edge["source_x"]
+        sy = locations[edge["source"]][1] + edge["source_y"]
+        tx = locations[edge["target"]][0] + edge["target_x"]
+        ty = locations[edge["target"]][1] + edge["target_y"]
+        midx = (tx - sx) / 2 + sx
+        midy = (ty - sy) / 2 + sy
+        data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" style="marker-end:url(#triangle)"/>' % (sx, sy, midx, midy)
+        data_content += '<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black"/>' % (midx, midy, tx, ty)
 
     for rec in rectangles:
         data_content += '<rect x="%s" y="%s" width="%s" height="%s" fill="white" stroke="black"/>' % (rec["x"], rec["y"], rec["width"], rec["height"])