Skip to content

Fixed hatching in PatchCollection class #27937

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

Impaler343
Copy link
Contributor

@Impaler343 Impaler343 commented Mar 16, 2024

PR summary

Closes #22654
Taking after @hhalwan's PR #22814, with a few of my own proposed changes and questions.
Have removed the determine_facecolor function as this is already checked for, by setting alpha to 0 if get_fill() is set to 0.
Have set edgecolor to black in case of no definition of edge-color in the patches, as it defaults to None, but shouldn't we be setting the edge-color to a color contrasting to the face-color? Is there a function that does this for us?
We can also re-route this PR by decoupling the hatch-color setting and edge-color setting(like in PR #26993), which might make the module clearer.
As we are choosing only the first patch's hatch, for all the patches if match_original is True, I don't think we need warnings as long as we update the API documentation. Thoughts?

Gives the needed output for:
#22654
image

PR checklist


# Using the hatch of only the first patch
if patches:
kwargs['hatch'] = patches[0].get_hatch()
Copy link
Member

Choose a reason for hiding this comment

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

This is still problematic because it broadcasts the hatch of the first patch to all of them, if we are matching original then we need to keep track of which (if any) of the patches were originaly hatched.

@tacaswell
Copy link
Member

Thank you for your work on this issue.

It definitely needs tests and while having any hatch is a step in the right direction, I think using the first hatch for all of the patches is about as wrong as using no-hatches so I think we should have a complete solution here.

If we can not broadcast hatch to each of of the patches (which may be a possibility given the vector nature of the collection artists) then we should clearly document that status-quo.

@Impaler343 Impaler343 closed this Apr 12, 2024
@Impaler343 Impaler343 deleted the hatch-patch branch April 12, 2024 04:24
@Impaler343 Impaler343 restored the hatch-patch branch April 12, 2024 04:30
@Impaler343 Impaler343 reopened this Apr 12, 2024
@Impaler343
Copy link
Contributor Author

Impaler343 commented Apr 17, 2024

Tried using an approach similar to the one used in drawing contours, drawing patches one at a time. This, however, takes away the speed of drawing as a collection. Tried messing with the C/C++ code and that did not end well. Seems like the function path_collection_generic() draws objects with similar properties only. Also added support for hatches in the form of a tuple.

@Impaler343
Copy link
Contributor Author

The RMS is very low(0.008) in the test that is failing, what to do?

@story645
Copy link
Member

story645 commented May 7, 2024

Could the test failure be related #10034?

@Impaler343 Impaler343 closed this Aug 8, 2024
@Impaler343 Impaler343 deleted the hatch-patch branch August 8, 2024 17:58
@Impaler343 Impaler343 restored the hatch-patch branch August 8, 2024 17:58
@Impaler343 Impaler343 reopened this Aug 10, 2024
@story645 story645 self-assigned this Aug 14, 2024
Copy link
Member

@story645 story645 left a comment

Choose a reason for hiding this comment

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

Hey, sorry for the delay here. Big thing is please add some tests to verify that the code is doing what you think it's supposed to.

@@ -990,7 +990,7 @@ def get_hatch(self):
def get_hatch_path(self, density=6.0):
"""Return a `.Path` for the current hatch."""
hatch = self.get_hatch()
if hatch is None:
if hatch is None or all(h is None for h in hatch):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if hatch is None or all(h is None for h in hatch):
if all(h is None for h in np.at_least1d(hatch)):

h is not None will trigger checking the second condition, which will then try to iterate over a maybe not iterable hatch.

@@ -990,7 +990,7 @@ def get_hatch(self):
def get_hatch_path(self, density=6.0):
"""Return a `.Path` for the current hatch."""
hatch = self.get_hatch()
if hatch is None:
if hatch is None or all(h is None for h in hatch):
return None
return Path.hatch(hatch, density)
Copy link
Member

Choose a reason for hiding this comment

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

will this need to be looped if passed a list of hatches?

@@ -171,6 +171,7 @@ def __init__(self, *,
# Flags set by _set_mappable_flags: are colors from mapping an array?
self._face_is_mapped = None
self._edge_is_mapped = None
self._match_original = False
Copy link
Member

Choose a reason for hiding this comment

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

where is this set?

Comment on lines 447 to 448
self.get_transforms(), offsets, offset_trf,
self.get_facecolor(), self.get_edgecolor(),
Copy link
Member

Choose a reason for hiding this comment

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

these return lists too, so why can't self.get_hatches, w/ changes as needed being implemented down in .draw_path_collection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense

Comment on lines +1868 to +1870
If True, use the colors, linewidths, linestyles
and the hatch of the original patches.
If False, new colors may be assigned by
Copy link
Member

Choose a reason for hiding this comment

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

how are new colors assigned if not passed in as part of the original patches?

kwargs['linestyles'] = [p.get_linestyle() for p in patches]
kwargs['antialiaseds'] = [p.get_antialiased() for p in patches]
self._match_original = True
kwargs['facecolors'] = tuple([p.get_facecolor() for p in patches])
Copy link
Member

Choose a reason for hiding this comment

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

why does it need to be a tuple?

@@ -180,7 +180,7 @@ def __init__(self, hatch, density):


def _validate_hatch_pattern(hatch):
valid_hatch_patterns = set(r'-+|/\xXoO.*')
valid_hatch_patterns = set(r'-+|/\xXoO.*').union({None})
Copy link
Member

Choose a reason for hiding this comment

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

why do you need to add None if next line you only validate if not none?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of a list of Nones, the set function makes it into a singleton set with None. So it fails the

if hatch is not None:

condition

Copy link
Member

Choose a reason for hiding this comment

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

Ok, but then I think you don't need the if hatch is not None anymore and can just go straight to validation

Comment on lines 1897 to 1900
# Edgecolors are handled separately because are defaulted to None
# and the Hatch colors depend on them.
if all(p._original_edgecolor is not None for p in patches):
kwargs["edgecolors"] = tuple([p.get_edgecolor() for p in patches])
Copy link
Member

Choose a reason for hiding this comment

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

how do you handle a patch collection where some edges are none and some aren't?

@r3kste
Copy link
Contributor

r3kste commented Aug 21, 2024

Sorry for the delay. We have been busy figuring out this PR as we didn't think this would get reviewed first.

For now, we have tried to implement self.get_hatches() down in draw_path_collection, as mentioned in this comment..

Weirdly, with the 'agg' backend, in some cases it works while in the others the code throws a segfault. We haven't been able to identify the cause for this behavior, so it would be really helpful if anyone could provide some insight onto why this might be happening.

If the above implementation doesn't work we would have to go back to the previous implementation, where draw_path_collection is called multiple times within a loop.

Meanwhile, it would be better if the changes made in #28104 gets reviewed, as it is complete and only needs tests as of now.

@story645
Copy link
Member

We have been busy figuring out this PR as we didn't think this would get reviewed first.

Sorry, this one was easier to review 😅

Meanwhile, it would be better if the changes made in #28104 gets reviewed, as it is complete and only needs tests as of now.

please don't wait on a review to add tests as tests will make it easier to review -> easier to trace through code given a usage example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: PatchCollection fails to keep hatch from Patch
4 participants