Skip to content

Rotate Markers in functions like plot, scatter, etcetera #19195

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

Closed
Polirecyliente opened this issue Dec 29, 2020 · 17 comments
Closed

Rotate Markers in functions like plot, scatter, etcetera #19195

Polirecyliente opened this issue Dec 29, 2020 · 17 comments
Labels
API: changes Good first issue Open a pull request against these issues if there are no active ones! New feature topic: markers
Milestone

Comments

@Polirecyliente
Copy link

Problem

Markers can't be rotated in a given angle, this would be useful even for a highschool level (I work making diagrams for a highschool level), and in general to make Matplotlib even better for making geometric diagrams.

Here is an example:

S03_03_Angle_markings

I did this diagram with Matplotlib, the code became cumbersome when trying to make the little angle markers, the ones that are two small lines perpendicular to the angle arcs. So I searched in the documentation and it seems there is no way to make marks like this builtin, there is no way to create a small perpendicular line to the arc in the middle of the arc, but then I thought about using plot markers for this.

The only problem of using markers, is that they can't be rotated, there is no way you can tell Matplotlib to make the marker perpendicular nor tangent to the line in which it's placed, or is there a way? I ask you kindly in case any of you know a way to plot markers perpendicular to the line in which they are placed.

Strangely this feature was being worked on since 2013, but it seems nothing came of it, see #2478

The way I have found to rotate markers, is using a private attribute from the markers called '_transform', this attribute has methods to rotate the marker, but it's private, this means that the API has not provided a way to access and change this attribute. Here is a minimal example to rotate the markers.

import matplotlib.pyplot as plt
from matplotlib.markers import MarkerStyle

list1 = [0, 1] #| x coordinates
list2 = [0, 0] #| y coordinates

p1 = [.5, 0] #| p1 stands for point1, the marker will be placed in this point
marker1 = MarkerStyle(r'$|||$')
marker1._transform.rotate_deg(45)

plt.plot(list1, list2, 'o-k', linewidth = 1)
plt.plot(p1[0], p1[1], 'k', marker = marker1)

#T# show the results
plt.show()

This outputs the following diagram
Figure_1

As can be seen, the marker is rotated 45 degrees, this marker of three small parallel lines is very common in geometric diagrams. The problem here is that I had to directly manipulate a private attribute, instead of doing that through a specific function for that purpose, such as a setter function.

Matplotlib should be capable of producing geometric diagrams, rotating markers is a basic necessity, and as I said before, I wish there could be a way to place the markers perpendicular or parallel to the line in which they are placed, so that the user wouldn't have to calculate the angle of rotation, but this isn't too bad, at least rotating the marker is necessary.

Proposed Solution

Add a wrapper function that acts as a setter for the rotation of markers. The issue #2478 probably has a better way than what I could come up with.

@mwaskom
Copy link

mwaskom commented Dec 31, 2020

I'm not sure if arbitrary markers can be rotated, but markers can take a generic (numsides, style, angle) tuple that seems to work for your purposes:

ax = plt.gca()
for i, angle in enumerate(np.arange(0, 360, 60)):
    ax.plot(i, i, marker=(1, 1, angle), ms=20, mew=2)

image

@Polirecyliente
Copy link
Author

@mwaskom no good sir, what you said is wrong, I challenge you to reproduce my image with your method... or don't waste your time and I explain you why, the tuple you used creates a regular polygon as a marker, i.e. a line (as you did) an equilateral triangle, a square, pentagon, hexagon, heptagon, etcetera... so you are not far, because if this tuple also accepted a "text" argument, them we would be set, in fact that could be a solution, add a "text" argument to this tuple, that rotates any given text as a marker.

@jklymak
Copy link
Member

jklymak commented Dec 31, 2020

I don't see any reason not to add (with proper documentation and tests of course)

    def rotate_deg(self, rotate):
        self._transform.rotate_deg(rotate)

to marker.py, and indeed I don't see why #2478 was so convoluted and didn't use the transform.

(Also, please @Polirecyliente, lets try not to "challenge" people who are trying to help!)

@mwaskom
Copy link

mwaskom commented Dec 31, 2020

no good sir, what you said is wrong, I challenge you to reproduce my image with your method

The problem in your example appeared to be placing small tick marks on a plot with arbitrary rotation. The more complicated marker is just three tick marks with the same angle and small offsets from each other. While not as simple as a single marker with a rotation parameter, that could solve your problem immediately rather than waiting on a change in matplotlib. One can also imagine writing a small function that abstracts the drawing of a triple tick mark in your code. But as you say, it seems like offering further helpful suggestions might be a waste of time...

@tacaswell
Copy link
Member

I think @jklymak suggestion of adding a rotation setting (and letting it come in through the__init__) is a step in the right direction (which will handle cases where we want to have the same rotation on every marker in either plot or scatter), but I also think we should revive #2478 again (which will handle the case where we want each marker in a collection to be rotated differently like we do in quiver / windbarb).

attn @brunobeltran

@tacaswell tacaswell added this to the v3.5.0 milestone Dec 31, 2020
@jklymak
Copy link
Member

jklymak commented Dec 31, 2020

Is this something @brunobeltran was working on?

A very quick look at collections.py seems there are quite a few ways it could be modernized, likely by just making the top-level class always have a self._transforms property, and making it a list of real transforms (sometimes it is just nx3x3 matrix). That may be a little wasteful for some artists that can't use it, but they could always zero it out. Oddly Collections.__init__ does not define a placeholder, but Collections.set_size sets self._transform.

Once that is all done, it seems set_rotations is an easy next step.

@tacaswell
Copy link
Member

Bruno has been doing a bunch of work on the Marker class.

@Polirecyliente
Copy link
Author

For the sake of completion, I must say that rotate_deg is not the only marker function that is private, there is also skew, translate, scale, and rotate (in radians), all those should be included, they are useful, I've used both rotate_deg and scale, and all of these are methods of the _transform property of markers.

@jklymak
Copy link
Member

jklymak commented Jan 1, 2021

Right, its the transform that is private, but passing around transforms can be problematic. But we could possibly take a transform argument when we make the marker.

@timhoffm
Copy link
Member

timhoffm commented Jan 1, 2021

But we could possibly take a transform argument when we make the marker.

That sounds reasonable. I'd add the limitation that a passed transform should not be modified afterwards (e.g. via set_matrix). I suppose we might want to combine the passed transform and the internal _transform and we don't want to keep track of both and possible updates. Also, changing marker transforms later will be an even more rare use case than just transforming markers at all.

The API would be something like

MarkerStyle(r'$|||$', transform=Affine2D().rotate_deg(45))

which is a bit bulky but IMHO still ok and it lets you specify any transform you want.

@jklymak
Copy link
Member

jklymak commented Jul 9, 2021

#20613 (comment) also makes the case that joinstyle should have a public interface.

@jklymak jklymak added topic: markers API: changes Good first issue Open a pull request against these issues if there are no active ones! labels Jul 9, 2021
@jklymak
Copy link
Member

jklymak commented Jul 9, 2021

marking as a "Good First Issue". Getting the API change right is a bit tricky, but not technically difficult. It will require tests, and ideally additional documentation wherever MarkerStyle is discussed in depth.

@tacaswell tacaswell modified the milestones: v3.5.0, v3.6.0 Aug 5, 2021
@Kuldeep-kd
Copy link

I would be very happy to contribute to this feature. From the above discussion I am thinking of two implementations,

  1. Implement the following function append_transform(**kwargs) or set_transform(arg).
  2. Implement public functions like skew, translate, scale, rotate, etc in the "matplotlib/markers.py" itself.

This will be my first contribution, I appreciate any help, if I have misunderstood anything or any suggestion will be great.

@deep-jkl deep-jkl mentioned this issue Aug 26, 2021
9 tasks
@deep-jkl
Copy link
Contributor

Hi, sorry to interrupt, I was slowly working on this issue last couple of weeks. I was slowed down by some issues with copying/deepcopying of Path.

@j-bowhay
Copy link
Contributor

Has #20914 resolved this?

@deep-jkl
Copy link
Contributor

I believe so.

@QuLogic
Copy link
Member

QuLogic commented Oct 30, 2021

Closing then, as that PR fixed it.

@QuLogic QuLogic closed this as completed Oct 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: changes Good first issue Open a pull request against these issues if there are no active ones! New feature topic: markers
Projects
None yet
Development

No branches or pull requests

9 participants