From 738012911d00aaed50f88cd2af6e37c1f2b09205 Mon Sep 17 00:00:00 2001 From: Daniel Rojas Date: Sun, 17 Oct 2021 18:13:59 +0200 Subject: [PATCH] Further refactor connector node generation --- src/wireviz/Harness.py | 2 - src/wireviz/wv_gv_html.py | 152 ++++++++++++++++++++++++++++++----- src/wireviz/wv_table_util.py | 31 +++++-- 3 files changed, 155 insertions(+), 30 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 6dab389..d65a667 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -178,13 +178,11 @@ class Harness: for connector in self.connectors.values(): # generate connector node gv_html = gv_node_connector(connector, self.options) - _default_fillcolor = translate_color(self.options.bgcolor_connector, "HEX") dot.node( connector.name, label=f"<\n{gv_html}\n>", shape="box", style="filled", - fillcolor=_default_fillcolor, ) # generate edges for connector loops if len(connector.loops) > 0: diff --git a/src/wireviz/wv_gv_html.py b/src/wireviz/wv_gv_html.py index 88e3ea4..949a818 100644 --- a/src/wireviz/wv_gv_html.py +++ b/src/wireviz/wv_gv_html.py @@ -13,17 +13,22 @@ HEADER_PN = "P/N" HEADER_MPN = "MPN" HEADER_SPN = "SPN" -# TODO: remove harness argument; only used by get_additional_component_table() -def gv_node_connector(connector: Connector, harness_options: Options) -> str: + +def gv_node_connector(connector: Connector, harness_options: Options) -> Table: # If no wires connected (except maybe loop wires)? if not (connector.ports_left or connector.ports_right): connector.ports_left = True # Use left side pins by default # generate all rows to be shown in the node if connector.show_name: - row_name = [ - f"{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}" - ] + str_name = [f"{remove_links(connector.name)}"] + if connector.bgcolor_title: + row_name_attribs = { + "bgcolor": translate_color(connector.bgcolor_title, "HEX") + } + row_name = [Td(str_name, attribs=row_name_attribs)] + else: + row_name = [str_name] else: row_name = [] @@ -34,16 +39,47 @@ def gv_node_connector(connector: Connector, harness_options: Options) -> str: ] row_pn = [html_line_breaks(cell) for cell in row_pn] + if connector.color: + colorbar_attribs = { + "bgcolor": translate_color(connector.color, "HEX"), + "width": 4, + } + colorbar_cell = Td("", attribs=colorbar_attribs) + else: + colorbar_cell = None + row_info = [ html_line_breaks(connector.type), html_line_breaks(connector.subtype), f"{connector.pincount}-pin" if connector.show_pincount else None, translate_color(connector.color, harness_options.color_mode), - html_colorbar(connector.color), + colorbar_cell, ] - row_image = [html_image(connector.image)] - row_image_caption = [html_caption(connector.image)] + # + # {html_size_attr(image)} + if connector.image and connector.image.caption: + # import pudb; pudb.set_trace() + row_image_attribs = html_size_attr_dict(connector.image) + row_image_attribs["balign"] = "left" + row_image_attribs["sides"] = "TLR" + if connector.image.bgcolor: + row_image_attribs["bgcolor"] = translate_color( + connector.image.bgcolor, "HEX" + ) + row_caption_attribs = {"balign": "left", "sides": "BLR"} + row_image = [Td(html_image_new(connector.image), attribs=row_image_attribs)] + row_image_caption = [ + Td( + html_caption_new(connector.image), + attribs=row_caption_attribs, + flat=True, + ) + ] + else: + row_image = [] + row_image_caption = [] row_notes = [html_line_breaks(connector.notes)] # row_additional_component_table = get_additional_component_table(self, connector) row_additional_component_table = None @@ -62,9 +98,12 @@ def gv_node_connector(connector: Connector, harness_options: Options) -> str: gv_pin_row(pinindex, pinname, pinlabel, pincolor, connector) ) - table_attribs = Attribs( - {"border": 0, "cellspacing": 0, "cellpadding": 3, "cellborder": 1} - ) + table_attribs = { + "border": 0, + "cellspacing": 0, + "cellpadding": 3, + "cellborder": 1, + } row_connector_table = str(Table(pin_rows, attribs=table_attribs)) else: row_connector_table = None @@ -80,19 +119,20 @@ def gv_node_connector(connector: Connector, harness_options: Options) -> str: row_notes, ] - html = "\n".join(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) + tbl = nested_table(rows) - return html + if connector.bgcolor: + tbl.attribs["bgcolor"] = translate_color(connector.bgcolor, "HEX") + elif harness_options.bgcolor_connector: + tbl.attribs["bgcolor"] = translate_color(harness_options.bgcolor_connector, "HEX") + + return tbl def gv_pin_row(pin_index, pin_name, pin_label, pin_color, connector): - cell_pin_left = Td( - pin_name, attribs=Attribs({"port": f"p{pin_index+1}l"}), flat=True - ) - cell_pin_label = Td(pin_label, flat=True) - cell_pin_right = Td( - pin_name, attribs=Attribs({"port": f"p{pin_index+1}r"}), flat=True - ) + cell_pin_left = Td(pin_name, attribs={"port": f"p{pin_index+1}l"}, flat=True) + cell_pin_label = Td(pin_label, flat=True, empty_is_none=True) + cell_pin_right = Td(pin_name, attribs={"port": f"p{pin_index+1}r"}, flat=True) cells = [ cell_pin_left if connector.ports_left else None, @@ -119,6 +159,39 @@ def gv_connector_loops(connector: Connector) -> List: return loop_edges +def nested_table(rows_in: List[Tr]): + outer_rows = [] + for row in rows_in: + if isinstance(row, List) and len(row) > 0 and any(row): + # remove rows which are none + row_no_empty = [cell for cell in row if cell is not None] + inner_cells = [] + for cell in row_no_empty: + if isinstance(cell, Td): + inner_cells.append(cell) + else: + inner_cell_attribs = {"balign": "left"} + inner_cells.append(Td(cell, attribs=inner_cell_attribs, flat=True)) + + inner_table_attribs = { + "border": 0, + "cellspacing": 0, + "cellpadding": 3, + "cellborder": 1, + } + if len(inner_cells) > 0: + inner_table = Table(Tr(inner_cells), attribs=inner_table_attribs) + outer_rows.append(Tr(Td(inner_table))) + elif row is not None and any(row): + outer_rows.append(Tr(Td(row))) + if len(outer_rows) == 0: + outer_rows = Tr(Td("")) # Generate empty cell to avoid GraphViz errors + outer_table_attribs = {"border": 0, "cellspacing": 0, "cellpadding": 0} + outer_table = Table(outer_rows, attribs=outer_table_attribs) + + return outer_table + + def nested_html_table( rows: List[Union[str, List[Optional[str]], None]], table_attrs: str = "" ) -> str: @@ -193,6 +266,24 @@ def html_image(image): return f"""' + if image.fixedsize: + # Close the preceeding tag and enclose the image cell in a table without + # borders to avoid narrow borders when the fixed width < the node width. + html = f""" + + +
{html}
+ """ + return f"{html_bgcolor_attr(image.bgcolor)}{html}" + + def html_caption(image): from wireviz.DataClasses import Image @@ -203,6 +294,12 @@ def html_caption(image): ) +def html_caption_new(image): + from wireviz.DataClasses import Image + + return f"{html_line_breaks(image.caption)}" if image and image.caption else None + + def html_size_attr(image): from wireviz.DataClasses import Image @@ -218,5 +315,20 @@ def html_size_attr(image): ) +def html_size_attr_dict(image): + # Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object + from wireviz.DataClasses import Image + + attr_dict = {} + if image: + if image.width: + attr_dict["width"] = image.width + if image.height: + attr_dict["height"] = image.height + if image.fixedsize: + attr_dict["fixedsize"] = "true" + return attr_dict + + def html_line_breaks(inp): return remove_links(inp).replace("\n", "
") if isinstance(inp, str) else inp diff --git a/src/wireviz/wv_table_util.py b/src/wireviz/wv_table_util.py index a3264c7..b5a36e6 100644 --- a/src/wireviz/wv_table_util.py +++ b/src/wireviz/wv_table_util.py @@ -24,13 +24,25 @@ class Tag: contents: str attribs: Attribs = field(default_factory=Attribs) flat: bool = False + empty_is_none: bool = False + + def __post_init__(self): + if self.attribs is None: + self.attribs = Attribs({}) + elif isinstance(self.attribs, Dict): + self.attribs = Attribs(self.attribs) + elif not isinstance(self.attribs, Attribs): + raise Exception( + "Tag.attribs must be of type None, Dict, or Attribs, " + f"but type {type(self.attribs).__name__} was given instead:\n" + f"{self.attribs}" + ) @property def tagname(self): return type(self).__name__.lower() def get_contents(self): - # import pudb; pudb.set_trace() separator = "" if self.flat else "\n" if isinstance(self.contents, Iterable) and not isinstance(self.contents, str): return separator.join([str(c) for c in self.contents if c is not None]) @@ -41,18 +53,21 @@ class Tag: def __repr__(self): separator = "" if self.flat else "\n" - html = [ - f"<{self.tagname}{str(self.attribs)}>", - self.get_contents(), - f"", - ] - return separator.join(html) + if self.contents is None and self.empty_is_none: + return "" + else: + html = [ + f"<{self.tagname}{str(self.attribs)}>", + self.get_contents(), + f"", + ] + return separator.join(html) @dataclass class TagSingleton(Tag): def __repr__(self): - return f"<{self.tagname}{self.attribs} />" + return f"<{self.tagname}{str(self.attribs)} />" @dataclass