Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support invisible nodes and fix arrows #1

Open
wants to merge 5 commits into
base: base-sha/0c0cf618533f05e19a2ea0ba81b4e8842864ea00
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,17 @@ python -m graphviz2drawio test/directed/hello.gv.txt

## Roadmap to [0.4](https://github.com/hbmartin/graphviz2drawio/milestone/2)
- [x] Improved Bezier curve support [done](https://github.com/hbmartin/graphviz2drawio/pull/81)
- [ ] Subgraph conversion #33
- [ ] Invisible node handling for edges #67
- [x] Invisible node handling for edges [#67](https://github.com/hbmartin/graphviz2drawio/issues/67) [done](https://github.com/hbmartin/graphviz2drawio/pull/83)
- [x] Image / tooltip support [#45](https://github.com/hbmartin/graphviz2drawio/issues/45) [done](https://github.com/hbmartin/graphviz2drawio/pull/82)
- [ ] Support for node with `path` shape #47
- [ ] Support for node with `path` shape [#47](https://github.com/hbmartin/graphviz2drawio/issues/47)
- [ ] Add py.typed file for type hinting
- [x] Fixes for no arrows and double arrows [done](https://github.com/hbmartin/graphviz2drawio/pull/83)

## Roadmap to 0.5
- [ ] Migrate to uv/hatch for packaging and dep mgmt
- [ ] Turn on master branch protection
- [ ] Text alignment inside of shape
- [ ] Improved support for stroke / background colors
- [ ] Ensure undirected graphs are not drawn with arrows
- [ ] Port layout/orientation

## Future Improvements
Expand Down
5 changes: 4 additions & 1 deletion graphviz2drawio/graphviz2drawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ def convert(graph_to_convert: AGraph | str | IO, layout_prog: str = "dot") -> st

svg_graph = graph.draw(prog=layout_prog, format="svg")

nodes, edges, clusters = parse_nodes_edges_clusters(svg_graph)
nodes, edges, clusters = parse_nodes_edges_clusters(
svg_data=svg_graph,
is_directed=graph.directed,
)

for e in edges:
e.enrich_from_graph(graph_edges[e.gid])
Expand Down
3 changes: 3 additions & 0 deletions graphviz2drawio/models/DotAttr.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
BACK = "back"
BOTH: str = "both"
FORWARD: str = "forward"
NONE = "none"

DIAMOND = "diamond"

Expand Down
18 changes: 5 additions & 13 deletions graphviz2drawio/models/Rect.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,21 @@ def __init__(
self.right = x + width
self.image = image

def x_ratio(self, search):
def x_ratio(self, search: float) -> float:
if search < self.x:
return 0
if search > self.x + self.width:
return 1
ratio = (search - self.x) / self.width
return self._approx(ratio, 0.5, 0.1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider keeping the _approx method and adding type annotations for clarity.

The new code is more complex and loses some of the original functionality. Specifically:

  1. The _approx method, which provided a way to approximate the ratio to a center value within a delta, has been removed. This might lead to less precise results in x_ratio and y_ratio methods.
  2. While type annotations are a good practice, they add a layer of complexity if the team is not familiar with them. However, this is a minor point compared to the loss of functionality.
  3. The original code had a clear separation of concerns with the _approx method, making it easier to understand and maintain. The new code inlines the ratio calculation, which can make it harder to modify or extend in the future.

To address these issues, consider keeping the _approx method and adding type annotations for clarity:

class Rectangle:
    def __init__(self, x: float, y: float, width: float, height: float, image: str) -> None:
        # x,y is the top left corner
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.bottom = y + height
        self.right = x + width
        self.image = image

    def x_ratio(self, search: float) -> float:
        if search < self.x:
            return 0
        if search > self.x + self.width:
            return 1
        ratio = (search - self.x) / self.width
        return self._approx(ratio, 0.5, 0.1)

    def y_ratio(self, search: float) -> float:
        if search < self.y:
            return 0
        if search > self.y + self.height:
            return 1
        ratio = (search - self.y) / self.height
        return self._approx(ratio, 0.5, 0.1)

    @staticmethod
    def _approx(value: float, center: float, delta: float) -> float:
        if abs(value - center) < delta:
            return center
        return value

    def to_dict_str(self) -> dict[str, str]:
        return {
            "x": str(self.x),
            "y": str(self.y),
            "width": str(self.width),
            "height": str(self.height),
        }

    def closest_point_along_perimeter(self, x: float, y: float) -> tuple[float, float]:
        x = clamp(x, self.x, self.right)
        y = clamp(y, self.y, self.bottom)
        return x, y

This approach maintains the original functionality and readability while adding type annotations for clarity.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment type correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment area correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of LLM test could this comment become?

  • 👍 - this comment is really good/important and we should always make it
  • 👎 - this comment is really bad and we should never make it
  • no reaction - don't turn this comment into an LLM test

return (search - self.x) / self.width

def y_ratio(self, search):
def y_ratio(self, search: float) -> float:
if search < self.y:
return 0
if search > self.y + self.height:
return 1
ratio = (search - self.y) / self.height
return self._approx(ratio, 0.5, 0.1)
return (search - self.y) / self.height

@staticmethod
def _approx(value, center, delta):
if abs(value - center) < delta:
return center
return value

def to_dict_str(self):
def to_dict_str(self) -> dict[str, str]:
return {
"x": str(self.x),
"y": str(self.y),
Expand Down
4 changes: 3 additions & 1 deletion graphviz2drawio/models/SvgParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@

def parse_nodes_edges_clusters(
svg_data: bytes,
*,
is_directed: bool,
) -> tuple[OrderedDict[str, Node], list[Edge], OrderedDict[str, Node]]:
root = ElementTree.fromstring(svg_data)[0]

coords = CoordsTranslate.from_svg_transform(root.attrib["transform"])
node_factory = NodeFactory(coords)
edge_factory = EdgeFactory(coords)
edge_factory = EdgeFactory(coords=coords, is_directed=is_directed)

nodes: OrderedDict[str, Node] = OrderedDict()
edges: OrderedDict[str, Edge] = OrderedDict()
Expand Down
74 changes: 73 additions & 1 deletion graphviz2drawio/mx/Edge.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from graphviz2drawio.models import DotAttr

from ..models.Rect import Rect
from . import MxConst
from .Curve import Curve
from .GraphObj import GraphObj
from .Styles import Styles
from .Text import Text


Expand All @@ -11,8 +14,10 @@ class Edge(GraphObj):
def __init__(
self,
sid: str,
*,
fr: str,
to: str,
is_directed: bool,
curve: Curve | None,
labels: list[Text],
) -> None:
Expand All @@ -21,10 +26,77 @@ def __init__(
self.to = to
self.curve = curve
self.line_style = None
self.dir = None
self.dir = DotAttr.FORWARD if is_directed else DotAttr.NONE
self.arrowtail = None
self.arrowhead = None
self.labels = labels

def get_edge_style(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider simplifying the code by reducing methods and parameters.

The new code introduces additional complexity that could be simplified. Here are some points to consider:

  1. Increased Number of Methods: The new methods (get_edge_style and _get_arrow_shape_and_fill) add complexity without significantly improving modularity. They are tightly coupled with the internal state of the Edge class.

  2. Additional Parameters: The get_edge_style method introduces source_geo and target_geo parameters, increasing cognitive load.

  3. Conditional Logic: The new methods contain multiple layers of conditional logic, making the code harder to follow and maintain.

  4. Duplication of Logic: There is some duplication in how styles are formatted and how arrow shapes and fills are determined, which could lead to maintenance issues.

  5. Increased Dependencies: The new code introduces dependencies on additional modules (Rect, MxConst, and Styles), increasing complexity and making it harder to test in isolation.

Consider simplifying the code by removing unnecessary parameters, reducing nested conditionals, and consolidating style formatting logic. This will make the code easier to read, understand, and maintain.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment type correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment area correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of LLM test could this comment become?

  • 👍 - this comment is really good/important and we should always make it
  • 👎 - this comment is really bad and we should never make it
  • no reaction - don't turn this comment into an LLM test

self,
source_geo: Rect | None,
target_geo: Rect | None,
) -> str:
dashed = 1 if self.line_style == DotAttr.DASHED else 0

end_arrow, end_fill = self._get_arrow_shape_and_fill(
arrow=self.arrowhead,
active_dirs={DotAttr.FORWARD, DotAttr.BOTH},
)
start_arrow, start_fill = self._get_arrow_shape_and_fill(
self.arrowtail,
active_dirs={DotAttr.BACK, DotAttr.BOTH},
)

if self.curve is not None:
style = Styles.EDGE.format(
dashed=dashed,
end_arrow=end_arrow,
end_fill=end_fill,
start_arrow=start_arrow,
start_fill=start_fill,
) + (MxConst.CURVED if self.curve.is_bezier else MxConst.SHARP)

if source_geo is not None:
exit_x, exit_y = source_geo.relative_location_along_perimeter(
self.curve.start,
)
style += f"exitX={exit_x:.4f};exitY={exit_y:.4f};"
if target_geo is not None:
entry_x, entry_y = target_geo.relative_location_along_perimeter(
self.curve.end,
)
style += f"entryX={entry_x:.4f};entryY={entry_y:.4f};"

return style

return Styles.EDGE.format(
end_arrow=end_arrow,
dashed=dashed,
end_fill=end_fill,
start_arrow=start_arrow,
start_fill=start_fill,
)

def _get_arrow_shape_and_fill(
self,
arrow: str | None,
active_dirs: set[str],
) -> tuple[str, int]:
shape = MxConst.BLOCK if self.dir in active_dirs else MxConst.NONE
fill = 1 if self.dir in active_dirs else 0

if arrow is not None:
if arrow == "none":
shape = MxConst.NONE
fill = 0
else:
if arrow[0] == DotAttr.NO_FILL:
fill = 0
if arrow[1:] == DotAttr.DIAMOND:
shape = MxConst.DIAMOND

return shape, fill

def curve_start_end(self):
if self.dir == DotAttr.BACK:
return self.curve.end, self.curve.start
Expand Down
13 changes: 11 additions & 2 deletions graphviz2drawio/mx/EdgeFactory.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from graphviz2drawio.models import SVG

from ..models.CoordsTranslate import CoordsTranslate
from ..models.Errors import MissingTitleError
from .CurveFactory import CurveFactory
from .Edge import Edge
from .Text import Text


class EdgeFactory:
def __init__(self, coords) -> None:
def __init__(self, coords: CoordsTranslate, *, is_directed: bool) -> None:
super().__init__()
self.curve_factory = CurveFactory(coords)
self.is_directed = is_directed

def from_svg(self, g) -> Edge:
title = SVG.get_title(g)
Expand All @@ -23,4 +25,11 @@ def from_svg(self, g) -> Edge:
if (path := SVG.get_first(g, "path")) is not None:
if "d" in path.attrib:
curve = self.curve_factory.from_svg(path.attrib["d"])
return Edge(sid=g.attrib["id"], fr=fr, to=to, curve=curve, labels=labels)
return Edge(
sid=g.attrib["id"],
fr=fr,
to=to,
is_directed=self.is_directed,
curve=curve,
labels=labels,
)
10 changes: 9 additions & 1 deletion graphviz2drawio/mx/GraphObj.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
_whitelist_attrs = ["dir"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider renaming _whitelist_attrs to _allowed_attrs.

The term 'whitelist' can be replaced with 'allowed' to use more inclusive language. Consider renaming _whitelist_attrs to _allowed_attrs.

Suggested change
_whitelist_attrs = ["dir"]
_allowed_attrs = ["dir"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment type correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment area correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of LLM test could this comment become?

  • 👍 - this comment is really good/important and we should always make it
  • 👎 - this comment is really bad and we should never make it
  • no reaction - don't turn this comment into an LLM test



class GraphObj:

def __init__(self, sid, gid) -> None:
self.sid = sid
self.gid = gid

def enrich_from_graph(self, attrs) -> None:
for k, v in attrs:
if k in self.__dict__ and self.__dict__[k] is not None:
if (
k not in _whitelist_attrs
and k in self.__dict__
and self.__dict__[k] is not None
):
continue
self.__setattr__(k, v)
75 changes: 20 additions & 55 deletions graphviz2drawio/mx/MxGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from xml.etree.ElementTree import Element, SubElement, indent, tostring

from graphviz2drawio.models import DotAttr
from graphviz2drawio.models.Rect import Rect
from graphviz2drawio.mx import MxConst
from graphviz2drawio.mx.Curve import Curve
from graphviz2drawio.mx.Edge import Edge
Expand All @@ -27,18 +26,26 @@ def __init__(self, nodes: OrderedDict[str, Node], edges: list[Edge]) -> None:

def add_edge(self, edge: Edge) -> None:
source, target = self.get_edge_source_target(edge)
style = self.get_edge_style(edge, source.rect, target.rect)
style = edge.get_edge_style(
source_geo=source.rect if source is not None else None,
target_geo=target.rect if target is not None else None,
)

attrib = {
"id": edge.sid,
"style": style,
"parent": "1",
"edge": "1",
}
if source is not None:
attrib["source"] = source.sid
if target is not None:
attrib["target"] = target.sid

edge_element = SubElement(
self.root,
MxConst.CELL,
attrib={
"id": edge.sid,
"style": style,
"parent": "1",
"edge": "1",
"source": source.sid,
"target": target.sid,
},
attrib=attrib,
)

if len(edge.labels) > 0:
Expand All @@ -58,52 +65,10 @@ def add_edge(self, edge: Edge) -> None:

self.add_mx_geo_with_points(edge_element, edge.curve)

def get_edge_source_target(self, edge: Edge) -> tuple[Node, Node]:
def get_edge_source_target(self, edge: Edge) -> tuple[Node | None, Node | None]:
if edge.dir == DotAttr.BACK:
return self.nodes[edge.to], self.nodes[edge.fr]
return self.nodes[edge.fr], self.nodes[edge.to]

@staticmethod
def get_edge_style(
edge: Edge, # pytype: disable=invalid-annotation
source_geo: Rect | None,
target_geo: Rect | None,
) -> str:
end_arrow = MxConst.BLOCK
end_fill = 1
dashed = 1 if edge.line_style == DotAttr.DASHED else 0
if edge.arrowtail is not None:
tail = edge.arrowtail
if edge.arrowtail[0] == DotAttr.NO_FILL:
end_fill = 0
tail = edge.arrowtail[1:]
if tail == DotAttr.DIAMOND:
end_arrow = MxConst.DIAMOND
if edge.curve is not None:
style = Styles.EDGE.format(
end_arrow=end_arrow,
dashed=dashed,
end_fill=end_fill,
) + (MxConst.CURVED if edge.curve.is_bezier else MxConst.SHARP)

if source_geo is not None:
exit_x, exit_y = source_geo.relative_location_along_perimeter(
edge.curve.start,
)
style += f"exitX={exit_x};exitY={exit_y};"
if target_geo is not None:
entry_x, entry_y = target_geo.relative_location_along_perimeter(
edge.curve.end,
)
style += f"entryX={entry_x};entryY={entry_y};"

return style

return Styles.EDGE.format(
end_arrow=end_arrow,
dashed=dashed,
end_fill=end_fill,
)
return self.nodes.get(edge.to), self.nodes.get(edge.fr)
Comment on lines +68 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider using Optional for type hinting.

Using Optional[Node] instead of Node | None can improve readability and consistency with common Python type hinting practices.

Suggested change
def get_edge_source_target(self, edge: Edge) -> tuple[Node | None, Node | None]:
if edge.dir == DotAttr.BACK:
return self.nodes[edge.to], self.nodes[edge.fr]
return self.nodes[edge.fr], self.nodes[edge.to]
@staticmethod
def get_edge_style(
edge: Edge, # pytype: disable=invalid-annotation
source_geo: Rect | None,
target_geo: Rect | None,
) -> str:
end_arrow = MxConst.BLOCK
end_fill = 1
dashed = 1 if edge.line_style == DotAttr.DASHED else 0
if edge.arrowtail is not None:
tail = edge.arrowtail
if edge.arrowtail[0] == DotAttr.NO_FILL:
end_fill = 0
tail = edge.arrowtail[1:]
if tail == DotAttr.DIAMOND:
end_arrow = MxConst.DIAMOND
if edge.curve is not None:
style = Styles.EDGE.format(
end_arrow=end_arrow,
dashed=dashed,
end_fill=end_fill,
) + (MxConst.CURVED if edge.curve.is_bezier else MxConst.SHARP)
if source_geo is not None:
exit_x, exit_y = source_geo.relative_location_along_perimeter(
edge.curve.start,
)
style += f"exitX={exit_x};exitY={exit_y};"
if target_geo is not None:
entry_x, entry_y = target_geo.relative_location_along_perimeter(
edge.curve.end,
)
style += f"entryX={entry_x};entryY={entry_y};"
return style
return Styles.EDGE.format(
end_arrow=end_arrow,
dashed=dashed,
end_fill=end_fill,
)
return self.nodes.get(edge.to), self.nodes.get(edge.fr)
def get_edge_source_target(self, edge: Edge) -> tuple[Optional[Node], Optional[Node]]:
if edge.dir == DotAttr.BACK:
return self.nodes.get(edge.to), self.nodes.get(edge.fr)
return self.nodes.get(edge.fr), self.nodes.get(edge.to)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment type correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment area correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of LLM test could this comment become?

  • 👍 - this comment is really good/important and we should always make it
  • 👎 - this comment is really bad and we should never make it
  • no reaction - don't turn this comment into an LLM test

return self.nodes.get(edge.fr), self.nodes.get(edge.to)

def add_node(self, node: Node) -> None:
fill = node.fill if node.fill is not None else MxConst.NONE
Expand Down
4 changes: 3 additions & 1 deletion graphviz2drawio/mx/Styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
# Make this subclass StrEnum when dropping Py 3.10 support
class Styles(Enum):
NODE = "verticalAlign=top;align=left;overflow=fill;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor={stroke};strokeWidth=1;fillColor={fill};"
EDGE = "html=1;endArrow={end_arrow};dashed={dashed};endFill={end_fill};"
EDGE = "html=1;endArrow={end_arrow};dashed={dashed};endFill={end_fill};startArrow={start_arrow};startFill={start_fill};"
EDGE_LABEL = (
"edgeLabel;html=1;align=center;verticalAlign=bottom;resizable=0;points=[];"
)
EDGE_INVIS = "rounded=1;html=1;exitX={exit_x:.3g};exitY={exit_y:.3g};jettySize=auto;curved={curved};endArrow={end_arrow};dashed={dashed};endFill={end_fill};"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring to reuse the existing EDGE style and only override the necessary properties.

The new code introduces a new style EDGE_INVIS which has a lot of overlap with the existing EDGE style, leading to increased duplication and maintenance complexity. Consider refactoring to reuse the existing EDGE style and only override the necessary properties. This will reduce duplication, improve maintainability, and ensure consistency. Here's a suggested refactor:

from enum import Enum
from . import Shape

class Styles(Enum):
    NODE = "verticalAlign=top;align=left;overflow=fill;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeColor={stroke};strokeWidth=1;fillColor={fill};"
    EDGE = "html=1;endArrow={end_arrow};dashed={dashed};endFill={end_fill};startArrow={start_arrow};startFill={start_fill};"
    EDGE_LABEL = "edgeLabel;html=1;align=center;verticalAlign=bottom;resizable=0;points=[];"
    TEXT = "margin:0px;text-align:{align};{margin};font-size:{size}px;font-family:{family};color:{color};"

    ELLIPSE = "ellipse;" + NODE
    CIRCLE = "ellipse;aspect=fixed;" + NODE
    HEXAGON = "shape=hexagon;perimeter=hexagonPerimeter2;" + NODE
    EGG = "shape=mxgraph.flowchart.display;direction=south;" + NODE
    TRIANGLE = "triangle;direction=north;" + NODE
    LINE = "line;strokeWidth=2;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;align=center;" + NODE

    @staticmethod
    def edge_invis(exit_x, exit_y, curved, end_arrow, dashed, end_fill):
        return f"rounded=1;html=1;exitX={exit_x:.3g};exitY={exit_y:.3g};jettySize=auto;curved={curved};" + Styles.EDGE.format(
            end_arrow=end_arrow, dashed=dashed, end_fill=end_fill, start_arrow="", start_fill=""
        )

This approach keeps the code DRY (Don't Repeat Yourself) and makes it more modular and maintainable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment type correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment area correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of LLM test could this comment become?

  • 👍 - this comment is really good/important and we should always make it
  • 👎 - this comment is really bad and we should never make it
  • no reaction - don't turn this comment into an LLM test


TEXT = "margin:0px;text-align:{align};{margin};font-size:{size}px;font-family:{family};color:{color};"

ELLIPSE = "ellipse;" + NODE
Expand Down
3 changes: 3 additions & 0 deletions graphviz2drawio/mx/Text.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ def from_svg(t):
bold=t.get("font-weight", None) == "bold",
italic=t.get("font-style", None) == "italic",
)

def __repr__(self) -> str:
return self.text
22 changes: 22 additions & 0 deletions test/directed/invisible.gv.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
digraph G {
graph [rankdir = LR, splines=ortho];

node[shape=record];

emmc [label="eMMC" color="blue"]
boot_host [label="" style=invis width=0 height=2]
spinor [label="SPI NOR" color="green"]
soc [label="SoC" height=4]
nand [label="Raw NAND" color="yellow"]
dev_eth [label="" style=invis width=0 height=2]
eeprom [label="I2C EEPROM" color="red"]

emmc -> soc [arrowhead=none]
boot_host -> soc [xlabel="Boot Source"]
boot_host -> soc [xlabel="USB" dir=both]
spinor -> soc [arrowhead=none]
soc -> nand [arrowhead=none]
soc -> dev_eth [xlabel="USB-OTG" dir=both]
soc -> dev_eth [xlabel="Ethernet" dir=both]
soc -> eeprom [arrowhead=none]
}
Loading