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 for gradients #94

Merged
merged 4 commits into from
Aug 15, 2024
Merged

Support for gradients #94

merged 4 commits into from
Aug 15, 2024

Conversation

hbmartin
Copy link
Owner

@hbmartin hbmartin commented Aug 15, 2024

Summary by Sourcery

Introduce support for SVG gradients in the graph visualization conversion process, allowing nodes and clusters to use gradient fills. Enhance the SVG parsing logic to extract and apply gradients, and improve error handling in color opacity adjustments.

New Features:

  • Add support for parsing and handling SVG gradients, including both radial and linear gradients, in the graph visualization to draw.io conversion process.

Enhancements:

  • Refactor the SVG parsing logic to include gradient extraction and application, allowing nodes and clusters to utilize gradient fills.
  • Improve error handling in color opacity adjustment by adding a try-except block to handle invalid hex color values.

Summary by CodeRabbit

  • New Features

    • Introduced a new function to find all matching SVG elements instead of just counting them.
    • Enhanced SVG parser to handle gradient definitions, improving visual representation.
    • Updated Node class to support both solid colors and gradient fills.
    • Improved SVG processing methods to integrate gradient handling for greater flexibility.
  • Bug Fixes

    • Added error handling to prevent crashes with invalid hexadecimal color inputs.
  • Documentation

    • Updated roadmap to reflect changes in feature priorities.
    • Updated linting rules for improved code quality monitoring.

Copy link
Contributor

sourcery-ai bot commented Aug 15, 2024

Reviewer's Guide by Sourcery

This pull request adds support for gradients in the graphviz2drawio library. The changes primarily affect the parsing of SVG elements and the creation of nodes with gradient fills. The implementation includes new functions for extracting gradient information from SVG elements and modifications to existing functions to handle gradient fills in nodes.

File-Level Changes

Files Changes
graphviz2drawio/models/SvgParser.py Added support for parsing and storing gradient information from SVG elements
graphviz2drawio/mx/NodeFactory.py
graphviz2drawio/mx/Node.py
Modified node creation process to handle gradient fills
graphviz2drawio/models/SvgParser.py Implemented new functions to extract gradient information from SVG elements
graphviz2drawio/mx/utils.py Updated color opacity adjustment function to handle potential ValueError
graphviz2drawio/models/SVG.py Replaced count_tags function with findall function in SVG utility class

Tips
  • Trigger a new Sourcery review by commenting @sourcery-ai review on the pull request.
  • Continue your discussion with Sourcery by replying directly to review comments.
  • You can change your review settings at any time by accessing your dashboard:
    • Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    • Change the review language;
  • You can always contact us if you have any questions or feedback.

Copy link
Contributor

coderabbitai bot commented Aug 15, 2024

Warning

Rate limit exceeded

@hbmartin has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 26 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Commits

Files that changed from the base of the PR and between ff0d471 and d43c2bd.

Walkthrough

The recent changes enhance the functionality and robustness of the SVG handling modules. Key updates include replacing the tag counting functionality with a list retrieval method, improving gradient parsing capabilities, and enhancing node styling to support gradient fills. Error handling was added to color adjustments, and new linting rules were introduced for better code quality. Collectively, these modifications promote greater versatility, usability, and maintainability in the SVG processing workflow.

Changes

Files Change Summary
.../models/SVG.py Replaced count_tags with findall to return matching SVG elements as a list instead of a count.
.../models/SvgParser.py Added gradient handling, modified parse_nodes_edges_clusters to include gradients, and introduced new extraction methods for color and gradients.
.../mx/Node.py Updated Node class to support Gradient type for the fill attribute, enhancing styling options for nodes.
.../mx/NodeFactory.py Modified from_svg to accept a gradients parameter. Refactored fill extraction logic to handle gradients dynamically.
.../mx/utils.py Implemented error handling in adjust_color_opacity to manage invalid hex color inputs gracefully.
pyproject.toml Added linting rules for SvgParser.py to monitor code quality related to complexity and parameter counts.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SvgParser
    participant NodeFactory
    participant Node

    User->>SvgParser: Parse SVG
    SvgParser->>NodeFactory: Create nodes with gradients
    NodeFactory->>Node: Create Node with fill/gradient
    Node-->>NodeFactory: Return Node
    NodeFactory-->>SvgParser: Return nodes and edges
    SvgParser-->>User: Return parsed SVG structure
Loading

🐰 Hopping through the code, oh what a sight!
New gradients and fills, colors shining bright.
From counting to fetching, the tags now do cheer,
With error handling hugs, no need for fear!
In the land of SVG, we leap and we play,
Thanks to these changes, hip-hip-hooray! 🌈✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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>.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @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.

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 as 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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @hbmartin - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 4 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

graphviz2drawio/models/SvgParser.py Show resolved Hide resolved
from ..models.Rect import Rect
from .GraphObj import GraphObj
from .MxConst import VERTICAL_ALIGN
from .Styles import Styles
from .Text import Text

Gradient: TypeAlias = tuple[str, str | None, str]
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider using a class or dataclass for Gradient instead of a tuple

Using a tuple for Gradient might lead to less self-documenting code. Consider defining a proper class or dataclass for Gradient, which would provide better type hinting and make the code more readable and maintainable.

from dataclasses import dataclass

@dataclass
class Gradient:
    start_color: str
    end_color: str | None
    direction: str

graphviz2drawio/mx/Node.py Outdated Show resolved Hide resolved
graphviz2drawio/mx/utils.py Show resolved Hide resolved
@@ -46,21 +50,29 @@
def get_node_style(self) -> str:
style_for_shape = Styles.get_for_shape(self.shape)
dashed = 1 if self.dashed else 0
additional_styling = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider using a dictionary to manage attributes for the Gradient type.

The new code introduces additional complexity due to type checking for the fill attribute, which can now be either a str or a Gradient tuple. This adds extra logic to handle different types, making the code harder to read and maintain. The get_node_style method now includes logic for the Gradient type, increasing cognitive load. Additionally, string concatenation for additional_styling can be error-prone and less efficient. To simplify, consider using a more structured approach to handle the Gradient type, such as using a dictionary to manage attributes and updating it in a single step if fill is a Gradient. This will improve readability and maintainability by reducing branching and string concatenation.

Copy link
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: 4

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 3986517 and bdfc2cf.

Files selected for processing (6)
  • graphviz2drawio/models/SVG.py (1 hunks)
  • graphviz2drawio/models/SvgParser.py (3 hunks)
  • graphviz2drawio/mx/Node.py (3 hunks)
  • graphviz2drawio/mx/NodeFactory.py (6 hunks)
  • graphviz2drawio/mx/utils.py (1 hunks)
  • pyproject.toml (1 hunks)
Additional comments not posted (8)
graphviz2drawio/models/SVG.py (1)

14-15: LGTM! But verify the function usage in the codebase.

The findall function provides more flexibility by returning a list of elements. Ensure that all code using count_tags has been updated to handle the new return type.

Verification successful

Function Usage Verified: findall Handles List Return Type Correctly

The findall function's usage throughout the codebase correctly handles the list of elements it returns. Instances in test/test_graphs.py, graphviz2drawio/models/SvgParser.py, and graphviz2drawio/mx/NodeFactory.py show appropriate handling such as iterating over the list and checking its length. No further action is needed.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all function calls to `findall` handle the new return type.

# Test: Search for the function usage. Expect: Code handling the list of elements returned.
rg --type python -A 5 $'findall'

Length of output: 62


Script:

#!/bin/bash
# Description: Verify all function calls to `findall` handle the new return type.

# Test: Search for the function usage. Expect: Code handling the list of elements returned.
rg --type py -A 5 $'findall'

Length of output: 2620

pyproject.toml (1)

32-32: Approved: Enhanced linting configuration.

Adding SvgParser.py to the linting configuration for complexity and parameter checks is a positive step towards maintaining code quality.

graphviz2drawio/mx/Node.py (2)

1-2: Addition of Gradient type alias.

The Gradient type alias is defined as a tuple of three strings, which seems appropriate for handling gradient information. Ensure that this aligns with the expected structure throughout the codebase.


19-19: Update to __init__ method to support gradients.

The fill parameter now accepts a str or Gradient, enhancing flexibility. Ensure that all instances of Node are updated to handle this change.

graphviz2drawio/mx/NodeFactory.py (2)

21-26: Modification of from_svg method to include gradients.

The method now accepts a gradients dictionary, allowing for more dynamic SVG processing. Ensure that the dictionary is correctly populated and used throughout the SVG parsing process.


100-105: Introduction of _extract_stroke method.

The separation of fill and stroke extraction improves clarity and maintainability. The method correctly adjusts stroke opacity. Ensure consistent usage across the codebase.

graphviz2drawio/models/SvgParser.py (2)

37-58: Enhancements in parse_nodes_edges_clusters for gradient handling.

The introduction of a gradients dictionary and its integration into node parsing improves SVG rendering capabilities. Ensure that gradient definitions are correctly identified and processed.


92-127: Gradient extraction logic in _extract_gradients.

The method effectively handles both radial and linear gradients, yielding structured gradient data. Consider optimizing the extraction process if performance becomes an issue with large SVG files.

graphviz2drawio/mx/utils.py Show resolved Hide resolved
graphviz2drawio/mx/Node.py Outdated Show resolved Hide resolved
Comment on lines +87 to +98
_fill_url_re = re.compile(r"url\(#([^)]+)\)")

@staticmethod
def _extract_fill_and_stroke(g: Element) -> tuple[str, str]:
def _extract_fill(g: Element, gradients: dict[str, Gradient]) -> str | Gradient:
fill = g.attrib.get("fill", MxConst.NONE)
stroke = g.attrib.get("stroke", MxConst.NONE)
if fill.startswith("url"):
match = NodeFactory._fill_url_re.search(fill)
if match is not None:
return gradients[match.group(1)]
if "fill-opacity" in g.attrib and fill != MxConst.NONE:
fill = adjust_color_opacity(fill, float(g.attrib["fill-opacity"]))
return fill
Copy link
Contributor

Choose a reason for hiding this comment

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

Tip

Codebase Verification

Enhance Error Handling in _extract_fill Method

The _extract_fill method currently lacks handling for cases where a gradient ID is not found in the gradients dictionary, which may result in a KeyError. Consider implementing error handling or a fallback mechanism to improve robustness.

  • File: graphviz2drawio/mx/NodeFactory.py
  • Lines: 87-98
Analysis chain

Enhancements in _extract_fill method for gradient handling.

The method correctly identifies and retrieves gradients using a regex pattern. Consider handling cases where the gradient ID is not found in the dictionary to prevent potential KeyError.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify handling of missing gradient IDs in `_extract_fill`.

# Test: Search for `_extract_fill` calls. Expect: Proper error handling or fallback for missing gradient IDs.
rg --type python -A 5 $'_extract_fill('

Length of output: 119


Script:

#!/bin/bash
# Corrected script to verify handling of missing gradient IDs in `_extract_fill`.

# Search for `_extract_fill` calls in the codebase. Expect proper error handling or fallback for missing gradient IDs.
rg --type python -A 5 '_extract_fill\('

Length of output: 72


Script:

#!/bin/bash
# Corrected script with the right file type for Python to verify handling of missing gradient IDs in `_extract_fill`.

# Search for `_extract_fill` calls in the codebase. Expect proper error handling or fallback for missing gradient IDs.
rg --type py -A 5 '_extract_fill\('

Length of output: 1595

Comment on lines 84 to 90
def _extract_stop_color(stop: ElementTree.Element) -> str | None:
if (color := _stop_color_re.search(stop.attrib["style"])) is not None:
if (opacity := _stop_opacity_re.search(stop.attrib["style"])) is not None:
return adjust_color_opacity(color.group(1), float(opacity.group(1)))
return color.group(1)
return None

Copy link
Contributor

Choose a reason for hiding this comment

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

Introduction of _extract_stop_color method.

This method encapsulates color and opacity extraction, enhancing modularity. Consider adding error handling for cases where style attributes are missing or malformed.

- if (color := _stop_color_re.search(stop.attrib["style"])) is not None:
+ if (style := stop.attrib.get("style")) and (color := _stop_color_re.search(style)):
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
def _extract_stop_color(stop: ElementTree.Element) -> str | None:
if (color := _stop_color_re.search(stop.attrib["style"])) is not None:
if (opacity := _stop_opacity_re.search(stop.attrib["style"])) is not None:
return adjust_color_opacity(color.group(1), float(opacity.group(1)))
return color.group(1)
return None
def _extract_stop_color(stop: ElementTree.Element) -> str | None:
if (style := stop.attrib.get("style")) and (color := _stop_color_re.search(style)):
if (opacity := _stop_opacity_re.search(style)) is not None:
return adjust_color_opacity(color.group(1), float(opacity.group(1)))
return color.group(1)
return None

Copy link
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

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between bdfc2cf and ff0d471.

Files selected for processing (2)
  • README.md (1 hunks)
  • graphviz2drawio/models/SvgParser.py (3 hunks)
Additional comments not posted (5)
README.md (1)

Line range hint 1-1:
Note the removal of fill gradient support from the roadmap.

The removal of the roadmap item for fill gradient support may imply a shift in project priorities or a reevaluation of this feature's importance. Ensure that this aligns with the project's current objectives.

graphviz2drawio/models/SvgParser.py (4)

37-37: Initialize gradients dictionary.

The initialization of the gradients dictionary is appropriate for collecting gradient definitions. Ensure that this dictionary is correctly populated and utilized throughout the function.


43-45: Ensure correct extraction and usage of gradients.

The extraction of gradients from both the root and group elements is crucial for applying them correctly. Verify that all necessary gradient definitions are captured and used appropriately.

Also applies to: 50-52


92-131: Verify support for all gradient types and data correctness.

The _extract_gradients function processes radial and linear gradients. Ensure that all necessary gradient types are supported and that the extracted data is correctly formatted for use in the application.

Verification successful

Function supports necessary gradient types and data extraction is correct.

The _extract_gradients function in SvgParser.py correctly handles radialGradient and linearGradient, which are the only gradient types present in the codebase. The extracted data is formatted appropriately for application use. No additional gradient types need to be supported at this time.

  • Location: graphviz2drawio/models/SvgParser.py
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify support for all gradient types and correctness of extracted data.

# Test: Search for radialGradient and linearGradient to ensure all types are handled.
rg --type python 'radialGradient|linearGradient'

Length of output: 81


Script:

#!/bin/bash
# Description: Verify support for all gradient types and correctness of extracted data.

# Search for radialGradient and linearGradient in the codebase.
rg 'radialGradient|linearGradient'

# Search for any other gradient types that might be used.
rg 'gradient'

Length of output: 24562


54-58: Incorporate gradients into node and cluster creation.

The integration of gradient data into the from_svg calls for nodes and clusters enhances their visual representation. Ensure that the NodeFactory correctly applies these gradients.

Also applies to: 71-75

graphviz2drawio/models/SvgParser.py Outdated Show resolved Hide resolved
hbmartin and others added 2 commits August 15, 2024 12:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Copy link

@hbmartin hbmartin merged commit d72aa3c into master Aug 15, 2024
9 checks passed
@hbmartin hbmartin deleted the add-gradient-support branch August 15, 2024 19:55
@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2024
@coderabbitai coderabbitai bot mentioned this pull request Nov 26, 2024
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