Skip to content

add path handling so that clusters/groups will display correctly#116

Merged
hbmartin merged 6 commits intomasterfrom
subgraphs
May 22, 2025
Merged

add path handling so that clusters/groups will display correctly#116
hbmartin merged 6 commits intomasterfrom
subgraphs

Conversation

@hbmartin
Copy link
Copy Markdown
Owner

@hbmartin hbmartin commented May 22, 2025

Summary by Sourcery

Add SVG path handling for accurate cluster/group rendering, improve DOT graph loading logic, adjust default image label positioning, and update package metadata.

New Features:

  • Compute node bounding rectangles from SVG path data for clusters/groups
  • Enhance graph loader to detect and load DOT graphs from filenames and inline strings

Enhancements:

  • Rename internal loader function to _load_pygraphviz_agraph
  • Position image node labels above images by default

Chores:

  • Add svg.path and diagrams to dependencies and update package keywords to include Lucidchart

Summary by CodeRabbit

  • New Features
    • Improved SVG path handling for node shapes, enabling accurate rectangle extraction from SVG <path> elements.
    • Added a new error type for handling SVG path parsing issues.
    • Added tests generating cloud architecture diagrams with the diagrams library and verifying conversion output.
  • Enhancements
    • Enhanced graph input detection, allowing more flexible loading of graph files and source strings based on file extensions and content.
  • Style
    • Updated image style to display labels above images instead of below.
  • Chores
    • Expanded package keywords and test dependencies for improved discoverability and testing.
    • Updated version to 1.1.0.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented May 22, 2025

Reviewer's Guide

This PR integrates SVG path-based rectangle computation for cluster/group rendering, refines graph input handling by renaming and extending the PyGraphviz loader, introduces path element support in NodeFactory, adjusts image label positioning, and updates package metadata for discoverability and testing.

Sequence Diagram for SVG Path Processing in NodeFactory

sequenceDiagram
    participant NF as NodeFactory.from_svg
    participant SVGUtil as SVG Utility
    participant RFP as rect_from_svg_path
    participant SPP as svg_path.parse_path
    participant CT as CoordsTranslate

    NF->>SVGUtil: get_first(g, "path")
    alt path element found
        SVGUtil-->>NF: return path_element
        NF->>RFP: rect_from_svg_path(self.coords, path.attrib["d"])
        RFP->>SPP: parse_path(path_d)
        SPP-->>RFP: parsed_path object
        RFP->>RFP: Calculate min/max x, y from parsed_path
        RFP->>CT: translate(min_x, min_y)
        CT-->>RFP: (translated_x, translated_y)
        RFP-->>NF: Rect(x, y, width, height)
        NF->>NF: Extract fill, stroke for new shape
    end
Loading

File-Level Changes

Change Details Files
Implemented path-based rectangle computation
  • Imported svg.path and added rect_from_svg_path
  • Parsed SVG path data to calculate bounding box and translate coordinates
graphviz2drawio/mx/RectFactory.py
Renamed and enhanced graph loader for DOT string detection
  • Renamed _load_pygraphviz_graph to _load_pygraphviz_agraph
  • Added logic to distinguish filenames from DOT strings based on extensions and braces
graphviz2drawio/graphviz2drawio.py
Added element support in NodeFactory
  • Handled nodes by extracting fill and stroke attributes
  • Used rect_from_svg_path to determine node rectangles and set shape to RECT
graphviz2drawio/mx/NodeFactory.py
Adjusted IMAGE node style positioning
  • Changed verticalLabelPosition from bottom to top
  • Ensured verticalAlign remains top for image labels
graphviz2drawio/mx/Styles.py
Updated package metadata for keywords and tests
  • Appended 'Lucidchart' to setup.py keywords
  • Included 'diagrams' in test dependencies
setup.py

Possibly linked issues

  • #0: PR implements SVG path handling to render clusters/groups defined by paths, fixing the issue of missing shapes in draw.io output.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 22, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

This update introduces new logic for handling SVG path elements in node factories, adds a utility for calculating bounding rectangles from SVG paths, and refines heuristics for loading pygraphviz graphs. It also adjusts style and metadata settings, including image label positioning and package keywords, updates project configuration files, adds a new error class, and increments the version.

Changes

File(s) Change Summary
.idea/misc.xml Added "PythonCompatibilityInspectionAdvertiser" component with version set to "3".
graphviz2drawio/graphviz2drawio.py Renamed _load_pygraphviz_graph to _load_pygraphviz_agraph; extended string input heuristics for loading pygraphviz AGraph objects; updated convert to use new function name.
graphviz2drawio/mx/NodeFactory.py Added support for SVG <path> elements in from_svg by using rect_from_svg_path to compute bounding rectangles; imported rect_from_svg_path.
graphviz2drawio/mx/RectFactory.py Introduced new function rect_from_svg_path to compute bounding rectangles from SVG path data.
graphviz2drawio/mx/Styles.py Changed IMAGE style's verticalLabelPosition from "bottom" to "top" in the Styles enum.
graphviz2drawio/models/Errors.py Added new exception class CouldNotParsePathError for errors parsing SVG paths.
graphviz2drawio/version.py Updated version string from "1.0.0" to "1.1.0".
setup.py Updated keywords (added "diagrams", "Lucidchart", removed "draw"); added "diagrams" to tests_require.
specs/directed/subgraph.xml Deleted static XML graph layout file representing a directed subgraph.
test/directed/subgraph.gv.txt Deleted static Graphviz DOT graph description file for a directed graph named "Layer1".
pyproject.toml Added lint ignore code "RSE102" to the ruff configuration.
.github/workflows/lint.yml Added installation of diagrams package before running pytest with coverage in lint workflow.
test/test_diagrams_generate.py Added new tests generating cloud architecture diagrams using diagrams and verifying conversion output.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant NodeFactory
    participant RectFactory

    User->>NodeFactory: from_svg(svg_group)
    NodeFactory->>NodeFactory: Detects <path> element
    NodeFactory->>RectFactory: rect_from_svg_path(coords, path_d)
    RectFactory-->>NodeFactory: Bounding Rect
    NodeFactory-->>User: Node with computed Rect and style
Loading
sequenceDiagram
    participant User
    participant graphviz2drawio
    participant pygraphviz

    User->>graphviz2drawio: convert(graph_input)
    graphviz2drawio->>graphviz2drawio: _load_pygraphviz_agraph(graph_input)
    alt graph_input is str with .dot/.gv/.txt
        graphviz2drawio->>pygraphviz: AGraph(filename)
    else graph_input is graph string
        graphviz2drawio->>pygraphviz: AGraph(string)
    else graph_input is other type
        graphviz2drawio->>pygraphviz: AGraph(graph_input)
    end
    graphviz2drawio-->>User: Converted output
Loading

Possibly related PRs

Poem

In the warren where code and SVG entwine,
A rabbit hops through paths and lines so fine.
Bounding rectangles now drawn with care,
While image labels rise to the top with flair.
New keywords sprout, dependencies grow—
In this meadow, improvements softly flow.
🐇✨
"""

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9c1600 and 3a9bef3.

📒 Files selected for processing (2)
  • .github/workflows/lint.yml (1 hunks)
  • test/test_diagrams_generate.py (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Hello @hbmartin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

Hello! Gemini here, providing a summary of this pull request by @hbmartin. This PR focuses on improving the conversion from Graphviz to Draw.io by adding specific handling for SVG path elements. This is particularly important for correctly rendering Graphviz clusters and groups, which are often represented as paths in the generated SVG output. The changes introduce logic to parse SVG path data (d attribute) to accurately determine the bounding box of these elements, ensuring they are positioned and sized correctly in the resulting Draw.io diagram.

Highlights

  • SVG Path Handling: Introduced new functionality to parse SVG <path> elements and calculate their bounding boxes based on the path data.
  • Cluster/Group Support: The primary goal of the SVG path handling is to correctly display Graphviz clusters and groups, which rely on path representations in the SVG output.
  • Node Factory Integration: Updated the node creation logic to recognize and process SVG path elements, utilizing the new path bounding box calculation.
  • Input Loading Refinement: Minor adjustments were made to the function that loads Graphviz inputs to better distinguish between string inputs representing filenames and those representing graph definitions.

Changelog

Click here to see the changelog
  • .idea/misc.xml
    • Added configuration for Python compatibility inspection component (lines 7-9).
  • graphviz2drawio/graphviz2drawio.py
    • Renamed _load_pygraphviz_graph to _load_pygraphviz_agraph (line 17).
    • Added explicit checks for file extensions (.dot, .gv, .txt) and graph string endings (}, } ) when loading graph inputs from strings (lines 58-61).
    • Added a comment clarifying string input type (line 70).
  • graphviz2drawio/mx/NodeFactory.py
    • Imported the new rect_from_svg_path function (line 13).
    • Added logic in the from_svg method to detect SVG <path> elements, calculate their bounding box using rect_from_svg_path, and extract fill/stroke styles (lines 72-76).
  • graphviz2drawio/mx/RectFactory.py
    • Imported the svg.path library (line 1).
    • Added a new function rect_from_svg_path that parses an SVG path data string (d attribute) and calculates the minimum bounding box containing all path segments (lines 7-22).
  • graphviz2drawio/mx/Styles.py
    • Changed the verticalLabelPosition for the IMAGE style from bottom to top (line 84).
  • setup.py
    • Added "diagrams" and "Lucidchart" to the project keywords (line 44).
    • Added "diagrams" to the tests_require dependencies (line 48).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


From dot to SVG,
A path appears, complex weave.
Draw.io now sees.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ellipsis-dev
Copy link
Copy Markdown

ellipsis-dev bot commented May 22, 2025

⚠️ This PR is too big for Ellipsis, but support for larger PRs is coming soon. If you want us to prioritize this feature, let us know at help@ellipsis.dev


Generated with ❤️ by ellipsis.dev

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces valuable new features, particularly the support for SVG path-based nodes and enhancements to graph loading. The code is generally well-structured. I've identified a critical issue regarding potential crashes and accuracy in SVG path parsing, and a couple of medium-severity points related to input handling and maintainability that would be good to address. Overall, good progress on extending the tool's capabilities!

Summary of Findings

  • Critical: SVG Path Bounding Box Calculation: In graphviz2drawio/mx/RectFactory.py, the rect_from_svg_path function has a critical issue: it can crash with an IndexError if an empty or invalid SVG path string is provided. Additionally, its current bounding box calculation method is inaccurate for paths with curves. Suggested using the svg.path.Path.bbox() method for correctness and robustness.
  • Medium: Heuristic for DOT String Detection: In graphviz2drawio/graphviz2drawio.py, the _load_pygraphviz_agraph function uses an endswith check for '}' or '}\n' to detect DOT strings, which is a heuristic and could potentially misclassify filenames. A more robust method, like prioritizing the existing regex check or try-catch parsing, was suggested for consideration.
  • Medium: Maintainability (Too Many Returns): In graphviz2drawio/graphviz2drawio.py, the _load_pygraphviz_agraph function is at the Pylint limit for return statements. While clear for now, further expansion might reduce readability. Suggested considering alternative dispatch patterns for long-term maintainability.
  • IDE Configuration Change: The change in .idea/misc.xml (adding PythonCompatibilityInspectionAdvertiser) is an IDE-specific configuration update. This is generally a low-impact change, likely for developer convenience. (Not commented due to severity settings)
  • Function Renaming: In graphviz2drawio/graphviz2drawio.py, _load_pygraphviz_graph was renamed to _load_pygraphviz_agraph. This is a good clarification. (Not commented due to severity settings)
  • NodeFactory Path Handling: In graphviz2drawio/mx/NodeFactory.py, new logic was added to detect <path> elements and use rect_from_svg_path. The shape is set to RECT, which aligns with the PR's goal of rendering paths as rectangular shapes based on their bounding box. (Not commented due to severity settings)
  • Style Updates: In graphviz2drawio/mx/Styles.py, the IMAGE style's verticalLabelPosition was updated. This is a minor visual adjustment. (Not commented due to severity settings)
  • Setup.py Updates: Changes in setup.py (keywords and test dependencies) are standard package metadata updates. (Not commented due to severity settings)

Merge Readiness

This pull request introduces significant improvements. However, there's a critical issue in RectFactory.py concerning SVG path parsing that could lead to crashes or incorrect renderings, and a couple of medium-severity issues that would improve robustness and maintainability. I recommend addressing at least the critical issue before merging. I am not authorized to approve pull requests, so please ensure these changes are reviewed and approved by a maintainer.

Comment on lines +8 to +18
parsed_path: svg_path.Path = svg_path.parse_path(path_d)
start: svg_path.Move = parsed_path.pop(0)
min_x = start.start.real
min_y = start.start.imag
max_x = start.start.real
max_y = start.start.imag
for e in parsed_path:
min_x = min(min_x, e.start.real, e.end.real)
min_y = min(min_y, e.start.imag, e.end.imag)
max_x = max(max_x, e.start.real, e.end.real)
max_y = max(max_y, e.start.imag, e.end.imag)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This implementation for calculating the SVG path's bounding box has a couple of potential issues:

  1. Unhandled Empty/Invalid Path: If svg_path.parse_path(path_d) (line 8) returns an empty list (e.g., for an invalid or empty path_d string), parsed_path.pop(0) on line 9 will raise an IndexError. It's crucial to check if parsed_path_obj (the result of parse_path) is empty before this operation to prevent a crash.
  2. Bounding Box Accuracy for Curves: The current loop (lines 14-18) calculates the bounding box using only the start and end points of path segments. This approach can lead to an incorrect (too small) bounding box for paths containing curves like Bezier segments or elliptical arcs, as their control points or the curve itself might extend beyond the AABB of just their start/end points.

Consider using the bbox() method provided by the svg.path.Path object. It typically handles all segment types correctly and would simplify this logic, addressing both points. How about the suggested refinement below?

Suggested change
parsed_path: svg_path.Path = svg_path.parse_path(path_d)
start: svg_path.Move = parsed_path.pop(0)
min_x = start.start.real
min_y = start.start.imag
max_x = start.start.real
max_y = start.start.imag
for e in parsed_path:
min_x = min(min_x, e.start.real, e.end.real)
min_y = min(min_y, e.start.imag, e.end.imag)
max_x = max(max_x, e.start.real, e.end.real)
max_y = max(max_y, e.start.imag, e.end.imag)
parsed_path_obj: svg_path.Path = svg_path.parse_path(path_d)
if not parsed_path_obj:
# It's important to handle cases where the path string is empty or invalid.
# Raising an error or returning a default (e.g., zero-sized) Rect are options.
# For this suggestion, let's raise a ValueError to make the issue explicit.
raise ValueError(f"Cannot compute bounding box for empty or invalid SVG path: {path_d}")
# Use the bbox() method from the svg.path library for accurate bounding box calculation.
# This method correctly handles all SVG path segment types, including curves (Bezier, arcs).
min_x, max_x, min_y, max_y = parsed_path_obj.bbox()
# The original code initialized min/max from the first 'Move' segment and then iterated.
# bbox() provides these values directly for the entire path.

Comment thread graphviz2drawio/graphviz2drawio.py
Comment on lines +60 to +61
if graph_to_convert.endswith(("}", "}\n")):
return AGraph(string=graph_to_convert)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The check graph_to_convert.endswith(("}", "}\n")) to identify if graph_to_convert is a DOT string seems like a heuristic. While it might work for many common cases, could there be edge cases where a filename (that isn't a DOT file and doesn't have a .dot/.gv/.txt extension) coincidentally ends with '}' or '}\n', leading to it being incorrectly treated as a DOT string? The subsequent regex check (pattern.search) is more robust for identifying DOT string content. Would it be safer to rely more on the regex for string content identification, or perhaps try parsing as a string first and fall back to filename if parsing fails, to avoid potential misclassification?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb55e9b and b441f02.

📒 Files selected for processing (6)
  • .idea/misc.xml (1 hunks)
  • graphviz2drawio/graphviz2drawio.py (3 hunks)
  • graphviz2drawio/mx/NodeFactory.py (2 hunks)
  • graphviz2drawio/mx/RectFactory.py (1 hunks)
  • graphviz2drawio/mx/Styles.py (1 hunks)
  • setup.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
graphviz2drawio/mx/NodeFactory.py (2)
graphviz2drawio/mx/RectFactory.py (4)
  • rect_from_ellipse_svg (59-65)
  • rect_from_image (47-56)
  • rect_from_svg_path (7-22)
  • rect_from_svg_points (25-44)
graphviz2drawio/models/SVG.py (1)
  • get_first (10-11)
🪛 Ruff (0.11.9)
setup.py

44-44: Line too long (104 > 88)

(E501)

🔇 Additional comments (9)
setup.py (2)

48-48: Added 'diagrams' to test dependencies.

Adding "diagrams" as a test dependency is appropriate since you're enhancing diagram-related functionality, particularly for clusters and groups. This ensures proper testing of the new features.


44-44:

Details

✅ Verification successful

Expanded keywords to better reflect package capabilities.

The keyword list was expanded to include "diagrams" and "Lucidchart", which better represents the package's capabilities, especially with the new path handling features being added.


🏁 Script executed:

#!/bin/bash
# Check if the keywords list exceeds PyPI's recommended length
keyword_length=$(echo "graphviz graph agraph dot convert conversion diagrams drawio mxgraph maxgraph Lucidchart" | wc -c)
echo "Keyword list length: $keyword_length characters"
# PyPI typically recommends keeping metadata fields concise
if [ $keyword_length -gt 100 ]; then
  echo "WARNING: Keyword list is quite long ($keyword_length chars). Consider shortening for better indexing."
fi

Length of output: 227


✅ Keywords list length is acceptable
The updated keywords string totals 89 characters, which is under PyPI’s informal 100-character guidance. The added terms “diagrams” and “Lucidchart” can remain.

🧰 Tools
🪛 Ruff (0.11.9)

44-44: Line too long (104 > 88)

(E501)

graphviz2drawio/mx/RectFactory.py (1)

1-2: Added SVG path handling capability.

Good addition of the svg.path library import, which enables the new path handling functionality.

graphviz2drawio/mx/NodeFactory.py (1)

10-15: Updated imports to include the new SVG path handling function.

The import statement is correctly updated to include the new rect_from_svg_path function along with the existing imported functions.

graphviz2drawio/mx/Styles.py (1)

84-84: Improved image label positioning.

Changing the vertical label position from "bottom" to "top" for images provides better visual alignment, particularly when working with clustered diagrams. This complements the other path handling improvements in this PR.

graphviz2drawio/graphviz2drawio.py (4)

17-17: Function call updated to match renamed function.

The function call was properly updated to use the new name _load_pygraphviz_agraph instead of the previous _load_pygraphviz_graph, maintaining consistency with the function definition below.


52-52: Improved function naming for clarity.

The function name change from _load_pygraphviz_graph to _load_pygraphviz_agraph is more specific and better reflects that it returns an AGraph object. The noqa: PLR0911 comment appropriately suppresses the linter warning about too many return statements, which is justified given the function's multiple code paths for different input types.


58-61: Enhanced string input handling with explicit heuristics.

The addition of specific handling for common file extensions and graph string patterns improves the robustness of the function. This change provides more predictable behavior when working with:

  1. Files with common Graphviz extensions (.dot, .gv, .txt)
  2. Graph strings that end with closing braces

This enhancement aligns with the PR objective of improving path handling for better display of clusters and groups.


70-70: Improved code clarity with descriptive comment.

The added comment clearly explains what the condition is checking for - that the input string contains a graph/dot definition. This improves code readability and makes the function's logic easier to understand for future maintainers.

Comment thread .idea/misc.xml
Comment on lines +7 to +9
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Review IDE configuration inclusion
The new <component name="PythonCompatibilityInspectionAdvertiser"> entry enables Python 3 compatibility inspections in JetBrains IDEs.
Please confirm that this IDE-specific setting should be checked into version control. If it’s not required for all contributors, consider removing it or excluding .idea/misc.xml via .gitignore to keep the repository clean.

🤖 Prompt for AI Agents
In the .idea/misc.xml file at lines 7 to 9, the addition of the
PythonCompatibilityInspectionAdvertiser component enables Python 3 compatibility
inspections in JetBrains IDEs. Verify if this IDE-specific configuration is
necessary for all contributors; if not, remove this component from the file or
exclude .idea/misc.xml from version control by adding it to .gitignore to avoid
cluttering the repository with personal IDE settings.

Comment thread graphviz2drawio/mx/RectFactory.py
Comment thread graphviz2drawio/mx/NodeFactory.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
graphviz2drawio/mx/RectFactory.py (1)

18-24: 🧹 Nitpick (assertive)

Consider using svg.path's bbox() method for improved accuracy.

The current implementation only considers start and end points of path segments, which may lead to inaccurate bounding boxes for curved segments as noted in your comment. The svg.path library provides a more accurate bbox() method.

-    # Note that this loop may not be accurate since it does not calculate
-    # the Bezier curve based on the control points
-    for e in parsed_path:
-        min_x = min(min_x, e.start.real, e.end.real)
-        min_y = min(min_y, e.start.imag, e.end.imag)
-        max_x = max(max_x, e.start.real, e.end.real)
-        max_y = max(max_y, e.start.imag, e.end.imag)
+    # Re-create the path with the start point
+    full_path = svg_path.Path([start] + list(parsed_path))
+    # Use the built-in bbox method for more accurate bounds calculation
+    min_x, max_x, min_y, max_y = full_path.bbox()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b441f02 and a0feb53.

📒 Files selected for processing (5)
  • graphviz2drawio/models/Errors.py (1 hunks)
  • graphviz2drawio/mx/RectFactory.py (1 hunks)
  • graphviz2drawio/version.py (1 hunks)
  • specs/directed/subgraph.xml (0 hunks)
  • test/directed/subgraph.gv.txt (0 hunks)
💤 Files with no reviewable changes (2)
  • specs/directed/subgraph.xml
  • test/directed/subgraph.gv.txt
🧰 Additional context used
🪛 GitHub Actions: Lint and Test
graphviz2drawio/mx/RectFactory.py

[error] 11-11: mypy type check error: Too few arguments for "CouldNotParsePathError" [call-arg]

🔇 Additional comments (3)
graphviz2drawio/mx/RectFactory.py (2)

1-2: Good addition of the svg.path dependency.

The import is correctly added to support the new SVG path handling functionality.


4-4: Appropriate import of the new error class.

The CouldNotParsePathError is properly imported from the models.Errors module.

graphviz2drawio/version.py (1)

1-1: Appropriate version bump for feature additions.

Incrementing the minor version number from 1.0.0 to 1.1.0 follows semantic versioning conventions for adding new functionality in a backward-compatible manner.

Comment on lines +8 to +28
def rect_from_svg_path(coords: CoordsTranslate, path_d: str) -> Rect:
parsed_path: svg_path.Path = svg_path.parse_path(path_d)
if len(parsed_path) == 0 or not isinstance(parsed_path[0], svg_path.Move):
raise CouldNotParsePathError
start: svg_path.Move = parsed_path.pop(0)
min_x = start.start.real
min_y = start.start.imag
max_x = start.start.real
max_y = start.start.imag

# Note that this loop may not be accurate since it does not calculate
# the Bezier curve based on the control points
for e in parsed_path:
min_x = min(min_x, e.start.real, e.end.real)
min_y = min(min_y, e.start.imag, e.end.imag)
max_x = max(max_x, e.start.real, e.end.real)
max_y = max(max_y, e.start.imag, e.end.imag)

(x, y) = coords.translate(min_x, min_y)

return Rect(x=x, y=y, width=max_x - min_x, height=max_y - min_y)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the error handling and consider adding a docstring.

The function implementation has two issues:

  1. When raising CouldNotParsePathError, you're not providing the required Element parameter (causing the mypy error)
  2. There's no docstring explaining the function's purpose and parameters
#!/bin/bash
# Check the CouldNotParsePathError definition
grep -A 10 "class CouldNotParsePathError" ../graphviz2drawio/models/Errors.py

For the first issue, either:

  1. Modify the CouldNotParsePathError to accept an optional Element parameter, or
  2. Create an Element object to pass to the error
-    if len(parsed_path) == 0 or not isinstance(parsed_path[0], svg_path.Move):
-        raise CouldNotParsePathError
+    if len(parsed_path) == 0 or not isinstance(parsed_path[0], svg_path.Move):
+        # Use a more appropriate error that doesn't require an Element
+        raise GdValueError(f"Could not parse path: {path_d}")

Additionally, add a docstring:

 def rect_from_svg_path(coords: CoordsTranslate, path_d: str) -> Rect:
+    """Calculate a bounding rectangle for an SVG path.
+    
+    Args:
+        coords: Coordinate translator for adjusting positions
+        path_d: SVG path data string (d attribute)
+        
+    Returns:
+        Rect: A rectangle encompassing the path
+        
+    Raises:
+        GdValueError: If the path cannot be parsed or does not start with a Move command
+    """
     parsed_path: svg_path.Path = svg_path.parse_path(path_d)
🧰 Tools
🪛 GitHub Actions: Lint and Test

[error] 11-11: mypy type check error: Too few arguments for "CouldNotParsePathError" [call-arg]

🤖 Prompt for AI Agents
In graphviz2drawio/mx/RectFactory.py between lines 8 and 28, fix the error
handling by passing the required Element parameter when raising
CouldNotParsePathError, either by creating an appropriate Element instance or
modifying the error class to accept an optional parameter. Also, add a clear
docstring at the start of the rect_from_svg_path function describing its
purpose, parameters, and return value.

Comment on lines +13 to +20
class CouldNotParsePathError(GdValueError):
"""Could not parse path from SVG element."""

def __init__(self, g: Element) -> None:
super().__init__(
f"Could not parse path from SVG element: {g.tag} ({g.attrib})",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Well-structured error class, but fix the inconsistency with its usage.

The new error class follows the pattern of other error classes in the file. However, there's an inconsistency with how it's used in RectFactory.py, where it's raised without the required Element parameter.

Either:

  1. Modify this class to accept an optional Element parameter, or
  2. Update the calling code in RectFactory.py to provide an Element
 class CouldNotParsePathError(GdValueError):
     """Could not parse path from SVG element."""
 
-    def __init__(self, g: Element) -> None:
+    def __init__(self, g: Element = None) -> None:
         super().__init__(
-            f"Could not parse path from SVG element: {g.tag} ({g.attrib})",
+            f"Could not parse path from SVG element: {g.tag if g else 'unknown'} ({g.attrib if g else 'no attributes'})",
         )

Alternatively, consider creating a more general version of this error for cases where an Element isn't available.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class CouldNotParsePathError(GdValueError):
"""Could not parse path from SVG element."""
def __init__(self, g: Element) -> None:
super().__init__(
f"Could not parse path from SVG element: {g.tag} ({g.attrib})",
)
class CouldNotParsePathError(GdValueError):
"""Could not parse path from SVG element."""
def __init__(self, g: Element = None) -> None:
super().__init__(
f"Could not parse path from SVG element: {g.tag if g else 'unknown'} ({g.attrib if g else 'no attributes'})",
)
🤖 Prompt for AI Agents
In graphviz2drawio/models/Errors.py around lines 13 to 20, the
CouldNotParsePathError class requires an Element parameter in its constructor,
but it is raised without this parameter in RectFactory.py, causing
inconsistency. To fix this, either modify the CouldNotParsePathError class to
accept an optional Element parameter and handle the case when it is None, or
update the RectFactory.py code to always provide the Element when raising this
error. Alternatively, create a more general error class for cases without an
Element and use that where appropriate.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
graphviz2drawio/mx/RectFactory.py (1)

8-28: 🧹 Nitpick (assertive)

Add a docstring to improve code documentation.

The new function works well for calculating bounding rectangles from SVG paths, but lacks a docstring explaining its purpose, parameters, and return value.

 def rect_from_svg_path(coords: CoordsTranslate, path_d: str) -> Rect:
+    """Calculate a bounding rectangle for an SVG path.
+    
+    Args:
+        coords: Coordinate translator for adjusting positions
+        path_d: SVG path data string (d attribute)
+        
+    Returns:
+        Rect: A rectangle encompassing the path
+        
+    Raises:
+        CouldNotParsePathError: If the path cannot be parsed or does not start with a Move command
+    """
     parsed_path: svg_path.Path = svg_path.parse_path(path_d)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0feb53 and 4820466.

📒 Files selected for processing (3)
  • graphviz2drawio/models/Errors.py (1 hunks)
  • graphviz2drawio/mx/RectFactory.py (1 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
graphviz2drawio/mx/RectFactory.py (3)
graphviz2drawio/models/CoordsTranslate.py (2)
  • CoordsTranslate (1-15)
  • translate (9-10)
graphviz2drawio/models/Errors.py (1)
  • CouldNotParsePathError (13-19)
graphviz2drawio/models/Rect.py (1)
  • Rect (1-62)
🔇 Additional comments (4)
pyproject.toml (1)

13-13: LGTM: Added "RSE102" to Ruff's ignore list.

The addition of "RSE102" to the lint.ignore list aligns with the introduction of the new CouldNotParsePathError class in this PR.

graphviz2drawio/models/Errors.py (1)

13-20: Good implementation of the new error class.

The CouldNotParsePathError follows the pattern of other error classes in the file and properly inherits from GdValueError. The constructor now accepts a path string parameter instead of an Element, which matches how it's used in RectFactory.py.

graphviz2drawio/mx/RectFactory.py (2)

1-5: Correct imports for the new functionality.

The imports for svg.path and CouldNotParsePathError are appropriate for the new SVG path handling functionality.


10-11: Error handling looks good.

The function correctly raises CouldNotParsePathError with the path data when the path is empty or doesn't start with a Move command, which matches the error class definition.

Comment on lines +18 to +25
# Note that this loop may not be accurate since it does not calculate
# the Bezier curve based on the control points
for e in parsed_path:
min_x = min(min_x, e.start.real, e.end.real)
min_y = min(min_y, e.start.imag, e.end.imag)
max_x = max(max_x, e.start.real, e.end.real)
max_y = max(max_y, e.start.imag, e.end.imag)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider using the svg.path library's bbox() method for more accurate results.

The current implementation calculates the bounding box using only the start and end points of path segments, which may not accurately capture the full extent of Bezier curves. The svg.path library provides a bbox() method that can handle curve control points more accurately.

-    # Note that this loop may not be accurate since it does not calculate
-    # the Bezier curve based on the control points
-    for e in parsed_path:
-        min_x = min(min_x, e.start.real, e.end.real)
-        min_y = min(min_y, e.start.imag, e.end.imag)
-        max_x = max(max_x, e.start.real, e.end.real)
-        max_y = max(max_y, e.start.imag, e.end.imag)
+    # Use the bbox() method for more accurate results with curves
+    # Create a new Path with the start element added back
+    full_path = svg_path.Path(start)
+    full_path.extend(parsed_path)
+    min_x, max_x, min_y, max_y = full_path.bbox()

🌐 Web query:

Does the svg.path library in Python provide a bbox() method for calculating bounding boxes of paths?

💡 Result:

The svg.path library in Python does not provide a built-in bbox() method for calculating the bounding box of paths or path segments. According to the official documentation and example usages, the library offers fundamental path segment classes (like Line, Arc, CubicBezier, QuadraticBezier) and a Path container, along with methods for parsing paths, evaluating points, and computing lengths[3]. However, there is no mention of a bounding box (bbox) method within its API or usage examples.

By contrast, libraries such as svgpathtools (which is sometimes referenced for similar tasks) do implement bbox() methods on individual path segments, and the community often uses it for bounding box calculations[1][5][7]. If you require bounding box functionality specifically in Python, you might consider switching to or supplementing with svgpathtools or svgelements, both of which provide more advanced geometric utilities—including bounding box calculations[1][6].

In summary:

  • svg.path does not provide a bbox() method out of the box for paths or segments[3].
  • For bounding box calculations in Python, use alternative libraries like svgpathtools or svgelements[1][6].

Citations:


Switch to a library with built-in bbox() or implement a manual bbox calculation

The svg.path library does not include a bbox() method, so the original suggestion to call full_path.bbox() on an svg.path.Path will not work. For accurate bounding boxes of Bezier curves you have two main options:

• Use svgpathtools (or svgelements) which provide a bbox() 方法 out of the box.
• Keep svg.path but compute extrema of each segment’s control points yourself.

File needing update:

  • graphviz2drawio/mx/RectFactory.py (around lines 18–25)

Suggested diff using svgpathtools:

-import svg.path as svg_path
+from svgpathtools import Path as SVGPath

     # Calculate bounding box including all control points
-    for e in parsed_path:
-        min_x = min(min_x, e.start.real, e.end.real)
-        min_y = min(min_y, e.start.imag, e.end.imag)
-        max_x = max(max_x, e.start.real, e.end.real)
-        max_y = max(max_y, e.start.imag, e.end.imag)
+    # parsed_path must be an svgpathtools.Path, not svg.path.Path
+    pt_path = SVGPath(*parsed_path)
+    min_x, max_x, min_y, max_y = pt_path.bbox()

If you prefer not to add a dependency, you’ll need to find the extremal points of each segment’s polynomial equations manually and factor in their control points.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In graphviz2drawio/mx/RectFactory.py around lines 18 to 25, the current bounding
box calculation only considers start and end points of path segments, which is
inaccurate for Bezier curves. Since svg.path lacks a bbox() method, fix this by
either switching to the svgpathtools or svgelements library that provide bbox()
methods for accurate bounding box calculation, or implement manual extrema
calculations for each segment's control points to determine the true bounding
box. Update the code to use one of these approaches to ensure the bounding box
fully encompasses the curves.

@sonarqubecloud
Copy link
Copy Markdown

@hbmartin hbmartin merged commit eb228b0 into master May 22, 2025
13 of 14 checks passed
@hbmartin hbmartin deleted the subgraphs branch May 22, 2025 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant