[Matplotlib-devel] What is draw_path's transform's destination?

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

[Matplotlib-devel] What is draw_path's transform's destination?

Rob McDonald
I'm implementing a custom path effect by inheriting from AbstractPathEffect and implementing draw_path as instructed here.  That link references RenderBase.draw_path to define the required interface.  Unfortunately, I can't find anywhere in the associated documentation that tells me what the transform argument really entails.

I've also read the transforms tutorial, and while that explains the pipeline and all the intermediate transformations, it doesn't really settle what I have available to me in draw_path.  It is clear that I can use it to transform coordinates from data to something -- but I don't know what to.

For the big picture -- I am implementing a path effect to allow drawing hatched lines similar to this, which I did years ago in Matlab.  Years before that, I've also done this in Java Graphics2D by implementing a custom stroke.  (The Java approach is more applicable to the path effect approach, but I can't point to that code online anywhere.)

Anyway, I have things mostly working -- and I am using the transform to go from data coordinates to something that looks orthogonal and reasonable on-screen.  So, for minimum functionality, it works.  However, I want to give the user the ability to control the length and spacing of the hatches -- which is in a coordinate system after the transformation is applied.  So, in order to document this thing, I need to know what it is.  Or, if there is a way to get intermediate stages of the transformation pipeline, that would work too.

As for the user interface -- it seems to make the most sense to specify the hatch length and spacing in terms of something similar to a line width (typically points) or a marker size.  I'm new to Python and matplotlib -- what is the Pythonic unit a user would expect to specify this in (and how do I achieve that with what is available in draw_path)?

Thanks,

Rob

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel
Reply | Threaded
Open this post in threaded view
|

Re: What is draw_path's transform's destination?

tcaswell
The transform is from whatever coordinates the path is in to pixels.  

From skimming the code on `Line2D` (and from the name) it looks like it is only the affine part of the path transformation.

Looking at the method `_offset_transform` in the base class also supports the target coordinate system of the transform is pixels.

That is a pretty nifty tool, is it going to end up someplace public when you are done?

Tom

On Sat, Mar 30, 2019 at 6:44 PM Rob McDonald <[hidden email]> wrote:
I'm implementing a custom path effect by inheriting from AbstractPathEffect and implementing draw_path as instructed here.  That link references RenderBase.draw_path to define the required interface.  Unfortunately, I can't find anywhere in the associated documentation that tells me what the transform argument really entails.

I've also read the transforms tutorial, and while that explains the pipeline and all the intermediate transformations, it doesn't really settle what I have available to me in draw_path.  It is clear that I can use it to transform coordinates from data to something -- but I don't know what to.

For the big picture -- I am implementing a path effect to allow drawing hatched lines similar to this, which I did years ago in Matlab.  Years before that, I've also done this in Java Graphics2D by implementing a custom stroke.  (The Java approach is more applicable to the path effect approach, but I can't point to that code online anywhere.)

Anyway, I have things mostly working -- and I am using the transform to go from data coordinates to something that looks orthogonal and reasonable on-screen.  So, for minimum functionality, it works.  However, I want to give the user the ability to control the length and spacing of the hatches -- which is in a coordinate system after the transformation is applied.  So, in order to document this thing, I need to know what it is.  Or, if there is a way to get intermediate stages of the transformation pipeline, that would work too.

As for the user interface -- it seems to make the most sense to specify the hatch length and spacing in terms of something similar to a line width (typically points) or a marker size.  I'm new to Python and matplotlib -- what is the Pythonic unit a user would expect to specify this in (and how do I achieve that with what is available in draw_path)?

Thanks,

Rob
_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel


--
Thomas Caswell
[hidden email]

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel
Reply | Threaded
Open this post in threaded view
|

Re: What is draw_path's transform's destination?

Rob McDonald
Thanks for the help,

How are 'pixels' handled when going to a vector back end?  I've seen some points_to_pixels methods around, but that isn't quite right.

Is the best thing to do to allow the user to set the hatch spacing in pixels?

I'll be happy to make it a contribution to the collection of path effects included in patheffects.py once I get things working smoothly.

For best effect (for my needs), it will also need to work well with contours.  I just found the docs that show that contours are already oriented (great news - I had to write a contour orienter in Matlab to get consistent behavior).  Unfortunately, naively passing path_effects=[HatchedStroke()] to a plt.contour() call doesn't 'just work'.  Is there a way to tell contour lines to be drawn with a different path effect?

Contributing this work presents a bit of a naming collision -- I generally call these hatched lines.  I would like to call this HatchedPathEffect or HatchedStroke when contributing it.  However, matplotlib uses hatching to refer to filling an area with a pattern.  While related, this is obviously different.  Should I call it something else? (what is Parseltongue for HatchedStroke?)

Rob


On Sat, Mar 30, 2019 at 11:12 PM Thomas Caswell <[hidden email]> wrote:
The transform is from whatever coordinates the path is in to pixels.  

From skimming the code on `Line2D` (and from the name) it looks like it is only the affine part of the path transformation.

Looking at the method `_offset_transform` in the base class also supports the target coordinate system of the transform is pixels.

That is a pretty nifty tool, is it going to end up someplace public when you are done?

Tom

On Sat, Mar 30, 2019 at 6:44 PM Rob McDonald <[hidden email]> wrote:
I'm implementing a custom path effect by inheriting from AbstractPathEffect and implementing draw_path as instructed here.  That link references RenderBase.draw_path to define the required interface.  Unfortunately, I can't find anywhere in the associated documentation that tells me what the transform argument really entails.

I've also read the transforms tutorial, and while that explains the pipeline and all the intermediate transformations, it doesn't really settle what I have available to me in draw_path.  It is clear that I can use it to transform coordinates from data to something -- but I don't know what to.

For the big picture -- I am implementing a path effect to allow drawing hatched lines similar to this, which I did years ago in Matlab.  Years before that, I've also done this in Java Graphics2D by implementing a custom stroke.  (The Java approach is more applicable to the path effect approach, but I can't point to that code online anywhere.)

Anyway, I have things mostly working -- and I am using the transform to go from data coordinates to something that looks orthogonal and reasonable on-screen.  So, for minimum functionality, it works.  However, I want to give the user the ability to control the length and spacing of the hatches -- which is in a coordinate system after the transformation is applied.  So, in order to document this thing, I need to know what it is.  Or, if there is a way to get intermediate stages of the transformation pipeline, that would work too.

As for the user interface -- it seems to make the most sense to specify the hatch length and spacing in terms of something similar to a line width (typically points) or a marker size.  I'm new to Python and matplotlib -- what is the Pythonic unit a user would expect to specify this in (and how do I achieve that with what is available in draw_path)?

Thanks,

Rob
_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel


--
Thomas Caswell
[hidden email]

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel
Reply | Threaded
Open this post in threaded view
|

Re: What is draw_path's transform's destination?

Rob McDonald
On Sat, Mar 30, 2019 at 11:51 PM Rob McDonald <[hidden email]> wrote:
Unfortunately, naively passing path_effects=[HatchedStroke()] to a plt.contour() call doesn't 'just work'.  Is there a way to tell contour lines to be drawn with a different path effect?


OK -- so I made some progress on this...

cp = plt.contour(x, y, z, colors=('k',), )
plt.setp(cp.collections, path_effects=[HatchedStroke()])

By and large works.  However, some of the hatches appear 'off' the lines.  I believe this is because some of the contour Path's might be generated as Bezier curves (not simple polylines).  I need to interpolate along the path to generate hatches, but I don't have a full renderer -- and Path.interpolate doesn't do curves.  I currently do the following to get to polylines

newpath = tpath.cleaned()
polys = newpath.to_polygons(closed_only=False)

Which generally works, but the default settings (deep inside agg from what I can tell (agg_curves.cpp etc.)) produce very coarse approximations that are rather unsatisfying.

Is there a better way to get a polyline from an arbitrary Path (that may include Bezier curves)?  Someone has to have implemented a compound path evaluation routine...

Alternately, is there a way to force contour to generate straight-line segments -- forcing the user to bump up the grid resolution if they want smooth curves?   Actually, that doesn't work.  I currently have an example with very fine resolution -- it appears that something in the Contour Path hierarchy is being clever and replacing the polyline segments with a smooth Bezier curve.  Possibly via a simplify call -- if there isn't a way to evaluate the true Path in draw_path, then perhaps there is a way to turn off the simplify step contour is doing...

Rob

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel
Reply | Threaded
Open this post in threaded view
|

Re: What is draw_path's transform's destination?

Rob McDonald
For the record, I was able to resolve this issue as well.

This SO answer references a response to a GitHub issue that gives the critical clue -- to_polygons has a hard-coded resolution of 1 because it expects to work in pixel space.  So, if I transform the Path before evaluating it, I get acceptable results.

Thanks,

Rob

On Sun, Mar 31, 2019 at 12:30 AM Rob McDonald <[hidden email]> wrote:
On Sat, Mar 30, 2019 at 11:51 PM Rob McDonald <[hidden email]> wrote:
Unfortunately, naively passing path_effects=[HatchedStroke()] to a plt.contour() call doesn't 'just work'.  Is there a way to tell contour lines to be drawn with a different path effect?


OK -- so I made some progress on this...

cp = plt.contour(x, y, z, colors=('k',), )
plt.setp(cp.collections, path_effects=[HatchedStroke()])

By and large works.  However, some of the hatches appear 'off' the lines.  I believe this is because some of the contour Path's might be generated as Bezier curves (not simple polylines).  I need to interpolate along the path to generate hatches, but I don't have a full renderer -- and Path.interpolate doesn't do curves.  I currently do the following to get to polylines

newpath = tpath.cleaned()
polys = newpath.to_polygons(closed_only=False)

Which generally works, but the default settings (deep inside agg from what I can tell (agg_curves.cpp etc.)) produce very coarse approximations that are rather unsatisfying.

Is there a better way to get a polyline from an arbitrary Path (that may include Bezier curves)?  Someone has to have implemented a compound path evaluation routine...

Alternately, is there a way to force contour to generate straight-line segments -- forcing the user to bump up the grid resolution if they want smooth curves?   Actually, that doesn't work.  I currently have an example with very fine resolution -- it appears that something in the Contour Path hierarchy is being clever and replacing the polyline segments with a smooth Bezier curve.  Possibly via a simplify call -- if there isn't a way to evaluate the true Path in draw_path, then perhaps there is a way to turn off the simplify step contour is doing...

Rob

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel
Reply | Threaded
Open this post in threaded view
|

Re: What is draw_path's transform's destination?

Rob McDonald
In reply to this post by tcaswell
Tom,

Sorry it has taken a while for me to come back to this, but better late than never.

I've attached what I came up with.  This is essentially my first foray into Python, so it likely has some warts beyond not being very Pythonic.

I haven't been able to get a local matplotlib build environment working -- so these aren't properly integrated into patheffects.py and test_patheffects.py.

I'm somewhat skeptical that you will want to introduce dependencies on scipy (1D interpolation) but I also didn't want to reinvent the wheel here.  Perhaps matplotlib has a 1D interpolator buried someplace.

Rob

P.S. Nathan and Steph say hi.


On Sat, Mar 30, 2019 at 9:12 PM Thomas Caswell <[hidden email]> wrote:
The transform is from whatever coordinates the path is in to pixels.  

From skimming the code on `Line2D` (and from the name) it looks like it is only the affine part of the path transformation.

Looking at the method `_offset_transform` in the base class also supports the target coordinate system of the transform is pixels.

That is a pretty nifty tool, is it going to end up someplace public when you are done?

Tom

On Sat, Mar 30, 2019 at 6:44 PM Rob McDonald <[hidden email]> wrote:
I'm implementing a custom path effect by inheriting from AbstractPathEffect and implementing draw_path as instructed here.  That link references RenderBase.draw_path to define the required interface.  Unfortunately, I can't find anywhere in the associated documentation that tells me what the transform argument really entails.

I've also read the transforms tutorial, and while that explains the pipeline and all the intermediate transformations, it doesn't really settle what I have available to me in draw_path.  It is clear that I can use it to transform coordinates from data to something -- but I don't know what to.

For the big picture -- I am implementing a path effect to allow drawing hatched lines similar to this, which I did years ago in Matlab.  Years before that, I've also done this in Java Graphics2D by implementing a custom stroke.  (The Java approach is more applicable to the path effect approach, but I can't point to that code online anywhere.)

Anyway, I have things mostly working -- and I am using the transform to go from data coordinates to something that looks orthogonal and reasonable on-screen.  So, for minimum functionality, it works.  However, I want to give the user the ability to control the length and spacing of the hatches -- which is in a coordinate system after the transformation is applied.  So, in order to document this thing, I need to know what it is.  Or, if there is a way to get intermediate stages of the transformation pipeline, that would work too.

As for the user interface -- it seems to make the most sense to specify the hatch length and spacing in terms of something similar to a line width (typically points) or a marker size.  I'm new to Python and matplotlib -- what is the Pythonic unit a user would expect to specify this in (and how do I achieve that with what is available in draw_path)?

Thanks,

Rob
_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel


--
Thomas Caswell
[hidden email]

_______________________________________________
Matplotlib-devel mailing list
[hidden email]
https://mail.python.org/mailman/listinfo/matplotlib-devel

tickedstroke.py (6K) Download Attachment
test_tickedstroke.py (1K) Download Attachment