From 608ddcecef3c161ece88082dcfb254bf72dcf778 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sat, 2 Jul 2022 12:17:37 +0200 Subject: [PATCH 1/6] fix: Improve the position of the conditions on the edges Fix #98 --- ansibleplaybookgrapher/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansibleplaybookgrapher/utils.py b/ansibleplaybookgrapher/utils.py index 511ef524..1f451c40 100644 --- a/ansibleplaybookgrapher/utils.py +++ b/ansibleplaybookgrapher/utils.py @@ -41,7 +41,7 @@ def convert_when_to_str(when: List) -> str: # Convert each element in the list to str when_to_str = list(map(str, when)) - return f"[when: {' and '.join(when_to_str)}]" + return f"[when: {' and '.join(when_to_str)}]".strip().replace("\n", "") def hash_value(value: str) -> str: From c7c09b3c6ccb87b8527789b785d85b5bc27e98d6 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Fri, 15 Jul 2022 23:46:03 +0200 Subject: [PATCH 2/6] fix: Improve text positions on the edges --- ansibleplaybookgrapher/postprocessor.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ansibleplaybookgrapher/postprocessor.py b/ansibleplaybookgrapher/postprocessor.py index c033d0d1..decc0820 100644 --- a/ansibleplaybookgrapher/postprocessor.py +++ b/ansibleplaybookgrapher/postprocessor.py @@ -162,20 +162,28 @@ def _curve_text_on_edges(self): for edge in edge_elements: path_element = edge.find(".//path", namespaces=self.root.nsmap) text_element = edge.find(".//text", namespaces=self.root.nsmap) + + # Define an ID for the path path_id = f"path_{edge.get('id')}" path_element.set("id", path_id) # Create a curved textPath text_path = etree.Element("textPath") text_path.set("{http://www.w3.org/1999/xlink}href", f"#{path_id}") - text_path.set("text-anchor", "middle") - text_path.set("startOffset", "50%") text_path.text = text_element.text + + # Depending on the text length, put the edge label near the node + if len(text_path.text) < 5: + # This is when the edge is only the counter (no when condition) + text_path.set("startOffset", "75%") + else: + text_path.set("startOffset", "60%") + text_element.append(text_path) # Move a little the text - text_element.set("dy", "-0.5%") - # Remove unnecessary attributes + text_element.set("dy", "-0.1%") + # Remove unnecessary attributes and clear the text text_element.attrib.pop("x") text_element.attrib.pop("y") text_element.text = None From 3984fbfd75cd7aef0cfdbb14a6270b00da202b01 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sat, 16 Jul 2022 22:42:15 +0200 Subject: [PATCH 3/6] move label --- ansibleplaybookgrapher/postprocessor.py | 41 ++++++++++++++++++++----- requirements.txt | 1 + 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/ansibleplaybookgrapher/postprocessor.py b/ansibleplaybookgrapher/postprocessor.py index decc0820..0476432b 100644 --- a/ansibleplaybookgrapher/postprocessor.py +++ b/ansibleplaybookgrapher/postprocessor.py @@ -15,10 +15,14 @@ import os from typing import Dict +from ansible.utils.display import Display from lxml import etree +from svg.path import parse_path from ansibleplaybookgrapher.graph import PlaybookNode +display = Display() + JQUERY = "https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" SVG_NAMESPACE = "http://www.w3.org/2000/svg" @@ -149,6 +153,31 @@ def _insert_graph_representation(self, graph_representation: PlaybookNode): element.append(root_subelement) + def _get_text_path_start_offset(self, path_element, text: str) -> str: + """ + + :param path_element: + :param text: + :return: + """ + # Get Bézier curve + path_segments = parse_path(path_element.get("d")) + # TODO: apply the translation to the segments + # self.root.xpath("//*[@id='graph0']", namespaces={"ns": SVG_NAMESPACE})[0].get("transform") + # The segments usually contain 3 elements: One MoveTo and one or two CubicBezier objects. + display.vvvvv(f"postprocessor: {len(path_segments)} segments found for the path '{path_element.get('id')}'") + # This relatively slow to compute. Decreasing the "error" will drastically slow down the post-processing + segment_length = path_segments.length(error=1e-4) + text_length = len(text) + # We divide the segments by 4 and put the label either: + # - at the middle of the curve if only one curve + # - at the middle of the last curve if the segment has 2 curves + offset_factor = len(path_segments) / 4 + + start_offset = segment_length * offset_factor - text_length / 2 + display.vvvvv(f"postprocessor: start_offset={start_offset}") + return str(start_offset) + def _curve_text_on_edges(self): """ Update the text on each edge to curve it based on the edge @@ -172,18 +201,14 @@ def _curve_text_on_edges(self): text_path.set("{http://www.w3.org/1999/xlink}href", f"#{path_id}") text_path.text = text_element.text - # Depending on the text length, put the edge label near the node - if len(text_path.text) < 5: - # This is when the edge is only the counter (no when condition) - text_path.set("startOffset", "75%") - else: - text_path.set("startOffset", "60%") + offset = self._get_text_path_start_offset(path_element, text_path.text) + text_path.set("startOffset", offset) text_element.append(text_path) # Move a little the text text_element.set("dy", "-0.1%") # Remove unnecessary attributes and clear the text - text_element.attrib.pop("x") - text_element.attrib.pop("y") + text_element.attrib.pop("x", "") + text_element.attrib.pop("y", "") text_element.text = None diff --git a/requirements.txt b/requirements.txt index 8c67469d..dd374e16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ ansible-core>=2.11,<2.13 graphviz>=0.18,<1 colour<1 lxml<5 +svg.path<7 \ No newline at end of file From 5171fdfa0640e270696d31e41eeaef92edd39908 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sun, 17 Jul 2022 17:18:03 +0200 Subject: [PATCH 4/6] Update --- ansibleplaybookgrapher/postprocessor.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ansibleplaybookgrapher/postprocessor.py b/ansibleplaybookgrapher/postprocessor.py index 0476432b..afbc8fd7 100644 --- a/ansibleplaybookgrapher/postprocessor.py +++ b/ansibleplaybookgrapher/postprocessor.py @@ -155,7 +155,7 @@ def _insert_graph_representation(self, graph_representation: PlaybookNode): def _get_text_path_start_offset(self, path_element, text: str) -> str: """ - + Get the start offset where the edge label should begin :param path_element: :param text: :return: @@ -163,19 +163,18 @@ def _get_text_path_start_offset(self, path_element, text: str) -> str: # Get Bézier curve path_segments = parse_path(path_element.get("d")) # TODO: apply the translation to the segments - # self.root.xpath("//*[@id='graph0']", namespaces={"ns": SVG_NAMESPACE})[0].get("transform") + # transform_attribute = self.root.xpath("//*[@id='graph0']", namespaces={"ns": SVG_NAMESPACE})[0].get("transform") # The segments usually contain 3 elements: One MoveTo and one or two CubicBezier objects. - display.vvvvv(f"postprocessor: {len(path_segments)} segments found for the path '{path_element.get('id')}'") - # This relatively slow to compute. Decreasing the "error" will drastically slow down the post-processing + # This is relatively slow to compute. Decreasing the "error" will drastically slow down the post-processing segment_length = path_segments.length(error=1e-4) text_length = len(text) - # We divide the segments by 4 and put the label either: - # - at the middle of the curve if only one curve - # - at the middle of the last curve if the segment has 2 curves - offset_factor = len(path_segments) / 4 + # We put the label closer to the target node + offset_factor = 0.76 - start_offset = segment_length * offset_factor - text_length / 2 - display.vvvvv(f"postprocessor: start_offset={start_offset}") + start_offset = segment_length * offset_factor - text_length + msg = f"postprocessor: {len(path_segments)} segments found for the path '{path_element.get('id')}', " + msg += f"segment_length={segment_length}, start_offset={start_offset}, text_length={text_length}" + display.vvvvv(msg) return str(start_offset) def _curve_text_on_edges(self): @@ -207,7 +206,7 @@ def _curve_text_on_edges(self): text_element.append(text_path) # Move a little the text - text_element.set("dy", "-0.1%") + text_element.set("dy", "-0.2%") # Remove unnecessary attributes and clear the text text_element.attrib.pop("x", "") text_element.attrib.pop("y", "") From e6b7d17c8c44e1778147d1e22e5df413dfa367f8 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sun, 17 Jul 2022 18:12:02 +0200 Subject: [PATCH 5/6] fix dy when using more than one path --- ansibleplaybookgrapher/postprocessor.py | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ansibleplaybookgrapher/postprocessor.py b/ansibleplaybookgrapher/postprocessor.py index afbc8fd7..37bd685a 100644 --- a/ansibleplaybookgrapher/postprocessor.py +++ b/ansibleplaybookgrapher/postprocessor.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os +import re from typing import Dict from ansible.utils.display import Display @@ -21,7 +22,11 @@ from ansibleplaybookgrapher.graph import PlaybookNode +# This a pattern to extract the "translate" transformation from the svg +TRANSLATE_PATTERN = re.compile(".*translate\((?P[+-]?[0-9]*[.]?[0-9]+) (?P[+-]?[0-9]*[.]?[0-9]+)\).*") + display = Display() +DISPLAY_PREFIX = "postprocessor:" JQUERY = "https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" SVG_NAMESPACE = "http://www.w3.org/2000/svg" @@ -162,8 +167,10 @@ def _get_text_path_start_offset(self, path_element, text: str) -> str: """ # Get Bézier curve path_segments = parse_path(path_element.get("d")) - # TODO: apply the translation to the segments - # transform_attribute = self.root.xpath("//*[@id='graph0']", namespaces={"ns": SVG_NAMESPACE})[0].get("transform") + # FIXME: apply the translation to the segments ? + # TRANSLATE_PATTERN = re.compile(".*translate\((?P[+-]?[0-9]*[.]?[0-9]+) (?P[+-]?[0-9]*[.]?[0-9]+)\).*") + # transform_attribute = self.root.xpath("//*[@id='graph0']", namespaces={"ns": SVG_NAMESPACE})[0].get("transform") + # The segments usually contain 3 elements: One MoveTo and one or two CubicBezier objects. # This is relatively slow to compute. Decreasing the "error" will drastically slow down the post-processing segment_length = path_segments.length(error=1e-4) @@ -172,7 +179,7 @@ def _get_text_path_start_offset(self, path_element, text: str) -> str: offset_factor = 0.76 start_offset = segment_length * offset_factor - text_length - msg = f"postprocessor: {len(path_segments)} segments found for the path '{path_element.get('id')}', " + msg = f"{DISPLAY_PREFIX} {len(path_segments)} segment(s) found for the path '{path_element.get('id')}', " msg += f"segment_length={segment_length}, start_offset={start_offset}, text_length={text_length}" display.vvvvv(msg) return str(start_offset) @@ -188,14 +195,19 @@ def _curve_text_on_edges(self): ) for edge in edge_elements: - path_element = edge.find(".//path", namespaces=self.root.nsmap) + path_elements = edge.findall(".//path", namespaces=self.root.nsmap) + display.vvvvv(f"{DISPLAY_PREFIX} {len(path_elements)} path(s) found on the edge '{edge.get('id')}'") text_element = edge.find(".//text", namespaces=self.root.nsmap) - # Define an ID for the path + # Define an ID for the path so that we can reference it explicitly path_id = f"path_{edge.get('id')}" + # Even though we may have more than one path, we only care about a single on. + # We have more than one path (edge) pointing to a single task if role containing the task is used more than + # once. + path_element = path_elements[0] path_element.set("id", path_id) - # Create a curved textPath + # Create a curved textPath: the text will follow the path text_path = etree.Element("textPath") text_path.set("{http://www.w3.org/1999/xlink}href", f"#{path_id}") text_path.text = text_element.text @@ -205,8 +217,9 @@ def _curve_text_on_edges(self): text_element.append(text_path) - # Move a little the text - text_element.set("dy", "-0.2%") + # The more paths we have, the more we move the text from the path + dy = -0.2 - (len(path_elements) - 1) * 0.4 + text_element.set("dy", f"{dy}%") # Remove unnecessary attributes and clear the text text_element.attrib.pop("x", "") text_element.attrib.pop("y", "") From 7916bf7e3d859bb600aaf81a0ae84d8b95ea3c77 Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sun, 17 Jul 2022 18:13:35 +0200 Subject: [PATCH 6/6] fix lint --- ansibleplaybookgrapher/postprocessor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ansibleplaybookgrapher/postprocessor.py b/ansibleplaybookgrapher/postprocessor.py index 37bd685a..40159acb 100644 --- a/ansibleplaybookgrapher/postprocessor.py +++ b/ansibleplaybookgrapher/postprocessor.py @@ -13,7 +13,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os -import re from typing import Dict from ansible.utils.display import Display @@ -22,9 +21,6 @@ from ansibleplaybookgrapher.graph import PlaybookNode -# This a pattern to extract the "translate" transformation from the svg -TRANSLATE_PATTERN = re.compile(".*translate\((?P[+-]?[0-9]*[.]?[0-9]+) (?P[+-]?[0-9]*[.]?[0-9]+)\).*") - display = Display() DISPLAY_PREFIX = "postprocessor:" @@ -196,7 +192,9 @@ def _curve_text_on_edges(self): for edge in edge_elements: path_elements = edge.findall(".//path", namespaces=self.root.nsmap) - display.vvvvv(f"{DISPLAY_PREFIX} {len(path_elements)} path(s) found on the edge '{edge.get('id')}'") + display.vvvvv( + f"{DISPLAY_PREFIX} {len(path_elements)} path(s) found on the edge '{edge.get('id')}'" + ) text_element = edge.find(".//text", namespaces=self.root.nsmap) # Define an ID for the path so that we can reference it explicitly