Replace bgcolor of node elements with box containing Look

In the elements: Image, AdditionalComponent, Connector, and Cable.
This commit is contained in:
KV 2021-10-03 17:37:38 +02:00
parent 251aab08ff
commit bb39a12256
4 changed files with 76 additions and 43 deletions

View File

@ -43,9 +43,10 @@ class Look:
fontsize: Optional[Points] = None fontsize: Optional[Points] = None
def _2dict(self) -> dict: def _2dict(self) -> dict:
"""Return dict of strings with color values translated to hex.""" """Return dict of non-None strings with color values translated to hex."""
return { return {
k:translate_color(v, "hex") if 'color' in k else str(v) for k,v in asdict(self).items() k:translate_color(v, "hex") if 'color' in k else str(v)
for k,v in asdict(self).items() if v is not None
} }
def graph_args(self) -> dict: def graph_args(self) -> dict:
@ -111,13 +112,16 @@ class Image:
width: Optional[Points] = None width: Optional[Points] = None
height: Optional[Points] = None height: Optional[Points] = None
fixedsize: Optional[bool] = None fixedsize: Optional[bool] = None
bgcolor: Optional[Color] = None box: Optional[Look] = None
# Contents of the text cell <td> just below the image cell: # Contents of the text cell <td> just below the image cell:
caption: Optional[MultilineHypertext] = None caption: Optional[MultilineHypertext] = None
# See also HTML doc at https://graphviz.org/doc/info/shapes.html#html # See also HTML doc at https://graphviz.org/doc/info/shapes.html#html
def __post_init__(self, gv_dir): def __post_init__(self, gv_dir):
if isinstance(self.box, dict):
self.box = Look(**self.box)
if self.fixedsize is None: if self.fixedsize is None:
# Default True if any dimension specified unless self.scale also is specified. # Default True if any dimension specified unless self.scale also is specified.
self.fixedsize = (self.width or self.height) and self.scale is None self.fixedsize = (self.width or self.height) and self.scale is None
@ -150,7 +154,11 @@ class AdditionalComponent:
qty: float = 1 qty: float = 1
unit: Optional[str] = None unit: Optional[str] = None
qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None qty_multiplier: Union[ConnectorMultiplier, CableMultiplier, None] = None
bgcolor: Optional[Color] = None box: Optional[Look] = None
def __post_init__(self) -> None:
if isinstance(self.box, dict):
self.box = Look(**self.box)
@property @property
def description(self) -> str: def description(self) -> str:
@ -160,8 +168,8 @@ class AdditionalComponent:
@dataclass @dataclass
class Connector: class Connector:
name: Designator name: Designator
bgcolor: Optional[Color] = None box: Optional[Look] = None
bgcolor_title: Optional[Color] = None title: Optional[Look] = None
manufacturer: Optional[MultilineHypertext] = None manufacturer: Optional[MultilineHypertext] = None
mpn: Optional[MultilineHypertext] = None mpn: Optional[MultilineHypertext] = None
supplier: Optional[MultilineHypertext] = None supplier: Optional[MultilineHypertext] = None
@ -188,6 +196,10 @@ class Connector:
def __post_init__(self) -> None: def __post_init__(self) -> None:
if isinstance(self.box, dict):
self.box = Look(**self.box)
if isinstance(self.title, dict):
self.title = Look(**self.title)
if isinstance(self.image, dict): if isinstance(self.image, dict):
self.image = Image(**self.image) self.image = Image(**self.image)
@ -246,8 +258,8 @@ class Connector:
@dataclass @dataclass
class Cable: class Cable:
name: Designator name: Designator
bgcolor: Optional[Color] = None box: Optional[Look] = None
bgcolor_title: Optional[Color] = None title: Optional[Look] = None
manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None manufacturer: Union[MultilineHypertext, List[MultilineHypertext], None] = None
mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None mpn: Union[MultilineHypertext, List[MultilineHypertext], None] = None
supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None supplier: Union[MultilineHypertext, List[MultilineHypertext], None] = None
@ -276,6 +288,10 @@ class Cable:
def __post_init__(self) -> None: def __post_init__(self) -> None:
if isinstance(self.box, dict):
self.box = Look(**self.box)
if isinstance(self.title, dict):
self.title = Look(**self.title)
if isinstance(self.image, dict): if isinstance(self.image, dict):
self.image = Image(**self.image) self.image = Image(**self.image)

View File

@ -12,7 +12,7 @@ from wireviz import wv_colors, __version__, APP_NAME, APP_URL
from wireviz.DataClasses import Metadata, Options, Tweak, Connector, Cable from wireviz.DataClasses import Metadata, Options, Tweak, Connector, Cable
from wireviz.wv_colors import get_color_hex, translate_color from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_gv_html import nested_html_table, \ from wireviz.wv_gv_html import nested_html_table, \
html_bgcolor_attr, html_bgcolor, html_colorbar, \ html_cell, html_colorbar, \
html_image, html_caption, remove_links, html_line_breaks html_image, html_caption, remove_links, html_line_breaks
from wireviz.wv_bom import pn_info_string, component_table_entry, \ from wireviz.wv_bom import pn_info_string, component_table_entry, \
get_additional_component_table, bom_list, generate_bom, \ get_additional_component_table, bom_list, generate_bom, \
@ -124,7 +124,7 @@ class Harness:
html = [] html = []
rows = [[f'{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}' rows = [[html_cell(connector.title, remove_links(connector.name))
if connector.show_name else None], if connector.show_name else None],
[pn_info_string(HEADER_PN, None, remove_links(connector.pn)), [pn_info_string(HEADER_PN, None, remove_links(connector.pn)),
html_line_breaks(pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn)), html_line_breaks(pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn)),
@ -139,7 +139,7 @@ class Harness:
[html_caption(connector.image)]] [html_caption(connector.image)]]
rows.extend(get_additional_component_table(self, connector)) rows.extend(get_additional_component_table(self, connector))
rows.append([html_line_breaks(connector.notes)]) rows.append([html_line_breaks(connector.notes)])
html.extend(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor))) html.extend(nested_html_table(rows, connector.box))
if connector.style != 'simple': if connector.style != 'simple':
pinhtml = [] pinhtml = []
@ -210,7 +210,7 @@ class Harness:
elif cable.gauge_unit.upper() == 'AWG': elif cable.gauge_unit.upper() == 'AWG':
awg_fmt = f' ({mm2_equiv(cable.gauge)} mm\u00B2)' awg_fmt = f' ({mm2_equiv(cable.gauge)} mm\u00B2)'
rows = [[f'{html_bgcolor(cable.bgcolor_title)}{remove_links(cable.name)}' rows = [[html_cell(cable.title, remove_links(cable.name))
if cable.show_name else None], if cable.show_name else None],
[pn_info_string(HEADER_PN, None, [pn_info_string(HEADER_PN, None,
remove_links(cable.pn)) if not isinstance(cable.pn, list) else None, remove_links(cable.pn)) if not isinstance(cable.pn, list) else None,
@ -233,7 +233,7 @@ class Harness:
rows.extend(get_additional_component_table(self, cable)) rows.extend(get_additional_component_table(self, cable))
rows.append([html_line_breaks(cable.notes)]) rows.append([html_line_breaks(cable.notes)])
html.extend(nested_html_table(rows, html_bgcolor_attr(cable.bgcolor))) html.extend(nested_html_table(rows, cable.box))
wirehtml = [] wirehtml = []
wirehtml.append('<table border="0" cellspacing="0" cellborder="0">') # conductor table wirehtml.append('<table border="0" cellspacing="0" cellborder="0">') # conductor table

View File

@ -4,9 +4,9 @@ from dataclasses import asdict
from itertools import groupby from itertools import groupby
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
from wireviz.DataClasses import AdditionalComponent, Cable, Connector from wireviz.DataClasses import AdditionalComponent, Cable, Connector, Look
from wireviz.wv_colors import Color, translate_color from wireviz.wv_colors import Color, translate_color
from wireviz.wv_gv_html import html_bgcolor_attr, html_line_breaks from wireviz.wv_gv_html import font_tag, html_line_breaks, table_attr
from wireviz.wv_helper import clean_whitespace from wireviz.wv_helper import clean_whitespace
BOM_COLUMNS_ALWAYS = ('id', 'description', 'qty', 'unit', 'designators') BOM_COLUMNS_ALWAYS = ('id', 'description', 'qty', 'unit', 'designators')
@ -35,7 +35,7 @@ def get_additional_component_table(harness: "Harness", component: Union[Connecto
common_args = { common_args = {
'qty': part.qty * component.get_qty_multiplier(part.qty_multiplier), 'qty': part.qty * component.get_qty_multiplier(part.qty_multiplier),
'unit': part.unit, 'unit': part.unit,
'bgcolor': part.bgcolor, 'box': part.box,
} }
if harness.options.mini_bom_mode: if harness.options.mini_bom_mode:
id = get_bom_index(harness.bom(), bom_entry_key({**asdict(part), 'description': part.description})) id = get_bom_index(harness.bom(), bom_entry_key({**asdict(part), 'description': part.description}))
@ -158,7 +158,7 @@ def component_table_entry(
type: str, type: str,
qty: Union[int, float], qty: Union[int, float],
unit: Optional[str] = None, unit: Optional[str] = None,
bgcolor: Optional[Color] = None, box: Optional[Look] = None,
pn: Optional[str] = None, pn: Optional[str] = None,
manufacturer: Optional[str] = None, manufacturer: Optional[str] = None,
mpn: Optional[str] = None, mpn: Optional[str] = None,
@ -178,8 +178,8 @@ def component_table_entry(
+ (', '.join([pn for pn in part_number_list if pn]))) + (', '.join([pn for pn in part_number_list if pn])))
# format the above output as left aligned text in a single visible cell # format the above output as left aligned text in a single visible cell
# indent is set to two to match the indent in the generated html table # indent is set to two to match the indent in the generated html table
return f'''<table border="0" cellspacing="0" cellpadding="3" cellborder="1"{html_bgcolor_attr(bgcolor)}><tr> return f'''<table border="0" cellspacing="0" cellpadding="3" cellborder="1"{table_attr(box)}><tr>
<td align="left" balign="left">{html_line_breaks(output)}</td> <td align="left" balign="left">{font_tag(box, html_line_breaks(output))}</td>
</tr></table>''' </tr></table>'''
def pn_info_string(header: str, name: Optional[str], number: Optional[str]) -> Optional[str]: def pn_info_string(header: str, name: Optional[str], number: Optional[str]) -> Optional[str]:

View File

@ -1,18 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import List, Optional, Union from typing import List, Optional, Union
import re
from wireviz.DataClasses import Image, Look
from wireviz.wv_colors import Color, translate_color from wireviz.wv_colors import Color, translate_color
from wireviz.wv_helper import remove_links from wireviz.wv_helper import remove_links
def nested_html_table(rows: List[Union[str, List[Optional[str]], None]], table_attrs: str = '') -> str: GvHtml = str # Graphviz HTML-like label string
# input: list, each item may be scalar or list GvHtmlX = str # Graphviz HTML-like label string possibly including a leading <tdX> tag
GvHtmlAttr = str # Attributes part of Graphviz HTML-like tag (including a leading space)
def nested_html_table(rows: List[Union[GvHtml, List[Optional[GvHtmlX]], None]], look: Optional[Look]) -> GvHtml:
# input: list, each item may be scalar or list, and look with optional table look attributes
# output: a parent table with one child table per parent item that is list, and one cell per parent item that is scalar # output: a parent table with one child table per parent item that is list, and one cell per parent item that is scalar
# purpose: create the appearance of one table, where cell widths are independent between rows # purpose: create the appearance of one table, where cell widths are independent between rows
# attributes in any leading <tdX> inside a list are injected into to the preceeding <td> tag # attributes in any leading <tdX> inside a list are injected into to the preceeding <td> tag
html = [] html = []
html.append(f'<table border="0" cellspacing="0" cellpadding="0"{table_attrs or ""}>') attr = font_attr(look)
font = f'<font{attr}>' if attr else ''
html.append(f'{font}<table border="0" cellspacing="0" cellpadding="0"{table_attr(look)}>')
for row in rows: for row in rows:
if isinstance(row, List): if isinstance(row, List):
if len(row) > 0 and any(row): if len(row) > 0 and any(row):
@ -28,23 +34,36 @@ def nested_html_table(rows: List[Union[str, List[Optional[str]], None]], table_a
html.append(' <tr><td>') html.append(' <tr><td>')
html.append(f' {row}') html.append(f' {row}')
html.append(' </td></tr>') html.append(' </td></tr>')
html.append('</table>') html.append(f'</table>{"</font>" if font else ""}')
return html return html
def html_bgcolor_attr(color: Color) -> str: def table_attr(look: Optional[Look]) -> GvHtmlAttr:
"""Return attributes for bgcolor or '' if no color.""" """Return table tag attributes containing all non-empty table option values."""
return f' bgcolor="{translate_color(color, "HEX")}"' if color else '' return '' if not look else ''.join({
f' {k}="{v}"' for k,v in look._2dict().items() if v and 'font' not in k})
def html_bgcolor(color: Color, _extra_attr: str = '') -> str: def font_attr(look: Optional[Look]) -> GvHtmlAttr:
"""Return <td> attributes prefix for bgcolor or '' if no color.""" """Return font tag attributes containing all non-empty font option values."""
return f'<tdX{html_bgcolor_attr(color)}{_extra_attr}>' if color else '' attr = {k:v for k,v in look._2dict().items() if v and 'font' in k} if look else {}
return ((f' color="{attr["fontcolor"]}"' if attr.get('fontcolor') else '')
+ (f' face="{attr["fontname"]}"' if attr.get('fontname') else '')
+ (f' point-size="{attr["fontsize"]}"' if attr.get('fontsize') else ''))
def html_colorbar(color: Color) -> str: def font_tag(look: Optional[Look], text: GvHtml) -> GvHtml:
"""Return <tdX> attributes prefix for bgcolor and minimum width or None if no color.""" """Return text in Graphviz HTML font tag with all non-empty font option values."""
return html_bgcolor(color, ' width="4"') if color else None attr = font_attr(look)
return f'<font{attr}>{text}</font>' if attr and text > '' else text
def html_image(image): def html_cell(look: Optional[Look], text: GvHtml = '', attr: GvHtmlAttr = '') -> GvHtmlX:
from wireviz.DataClasses import Image """Return cell to be included in the rows list for nested_html_table()."""
return f'<tdX{attr}{table_attr(look)}>{font_tag(look, text)}'
def html_colorbar(color: Optional[Color]) -> Optional[GvHtmlX]:
"""Return colored cell to be included in the rows list for nested_html_table() or None if no color."""
return html_cell(Look(bgcolor=color), attr=' width="4"') if color else None
def html_image(image: Optional[Image]) -> Optional[GvHtmlX]:
"""Return image cell to be included in the rows list for nested_html_table() or None if no image."""
if not image: if not image:
return None return None
# The leading attributes belong to the preceeding tag. See where used below. # The leading attributes belong to the preceeding tag. See where used below.
@ -57,16 +76,14 @@ def html_image(image):
<td{html}</td> <td{html}</td>
</tr></table> </tr></table>
''' '''
return f'''<tdX{' sides="TLR"' if image.caption else ''}{html_bgcolor_attr(image.bgcolor)}{html}''' return f'''<tdX{' sides="TLR"' if image.caption else ''}{table_attr(image.box)}{html}'''
def html_caption(image): def html_caption(image: Optional[Image]) -> Optional[GvHtmlX]:
from wireviz.DataClasses import Image """Return image caption cell to be included just after the image cell or None if no caption."""
return (f'<tdX sides="BLR"{html_bgcolor_attr(image.bgcolor)}>{html_line_breaks(image.caption)}' return html_cell(image.box, html_line_breaks(image.caption), ' sides="BLR"') if image and image.caption else None
if image and image.caption else None)
def html_size_attr(image): def html_size_attr(image: Optional[Image]) -> GvHtmlAttr:
from wireviz.DataClasses import Image """Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object."""
# Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object
return ((f' width="{image.width}"' if image.width else '') return ((f' width="{image.width}"' if image.width else '')
+ (f' height="{image.height}"' if image.height else '') + (f' height="{image.height}"' if image.height else '')
+ ( ' fixedsize="true"' if image.fixedsize else '')) if image else '' + ( ' fixedsize="true"' if image.fixedsize else '')) if image else ''