Opened 12 years ago
Closed 9 years ago
#1431 closed enhancement (fixed)
basic plotting: add support for setting the location and labels of all tick marks on the x and y axes
Reported by: | was | Owned by: | kcrisman |
---|---|---|---|
Priority: | major | Milestone: | sage-4.6 |
Component: | graphics | Keywords: | |
Cc: | Merged in: | sage-4.6.alpha1 | |
Authors: | Jason Grout, Karl-Dieter Crisman | Reviewers: | Karl-Dieter Crisman |
Report Upstream: | N/A | Work issues: | |
Branch: | Commit: | ||
Dependencies: | Stopgaps: |
Description (last modified by )
Amazingly some very basic functionality for plotting hasn't yet been implemented! In particular, there is nothing yet for setting the location and labels of all tick marks on the x and y axes. This needs to be implemented.
Attachments (4)
Change History (40)
comment:1 Changed 12 years ago by
- Component changed from algebraic geometry to graphics
comment:2 Changed 10 years ago by
Changed 10 years ago by
comment:3 Changed 10 years ago by
- Status changed from new to needs_work
Still needs doctest examples.
Example:
def multiple_of_pi(n,pos): m = n*1.0/float(pi) c=[i for i in convergents(m) if i.denominator()<12] q=c[-1] if q.denominator()==1: if q.numerator()==1: return r'$\pi$' elif q.numerator()==-1: return r'$-\pi$' else: return r'$%s\pi$'%q else: if q.numerator()==1: return r'$\frac{\pi}{%s}$'%q.denominator() elif q.numerator()==-1: return r'$\frac{-\pi}{%s}$'%q.denominator() else: return r'$\frac{%s\pi}{%s}$'%(q.numerator(),q.denominator()) from matplotlib.ticker import MultipleLocator, FuncFormatter plot(sin(x), (x, -4, 4), tick_locator=MultipleLocator(float(pi/3)), tick_formatter=FuncFormatter(multiple_of_pi))
comment:4 Changed 10 years ago by
comment:5 Changed 10 years ago by
This looks like a great start. Now we need a wrapper to make creating the formatters and locators easy for the average user. I will try to help with that, and documentation, as I get a chance. Thanks!
comment:6 Changed 10 years ago by
Personally, I think creating formatters and locators *is* easy. Notice what I did to create the locator: MultipleLocator(float(pi/3))
. The formatter is slightly harder because it's a harder problem I was trying to solve---given a floating point number, find a nice multiple of pi expression for a label. In general, though, it's easy to just give a list of tick values and strings.
What did you have in mind for an easier wrapper?
comment:7 Changed 10 years ago by
A simpler example:
def multiple_of_pi(n,pos): m = n*1.0/float(pi) c=[i for i in convergents(m) if i.denominator()<12] q=c[-1] return '$%s$'%latex(q*pi) from matplotlib.ticker import MultipleLocator, FuncFormatter plot(sin(x), (x, -4, 4), tick_locator=MultipleLocator(float(pi/4)), tick_formatter=FuncFormatter(multiple_of_pi))
comment:8 Changed 10 years ago by
See #4529 for a related ticket---logarithmic scales.
comment:9 Changed 10 years ago by
One last one that is even simpler:
def multiple_of_pi(n,pos): c=[i for i in convergents(n/pi) if i.denominator()<=12] return '$%s$'%latex(c[-1]*pi) from matplotlib.ticker import MultipleLocator, FuncFormatter plot(sin(x), (x, -10, 10), tick_locator=MultipleLocator(float(pi/2)), tick_formatter=FuncFormatter(multiple_of_pi),figsize=[10,4])
comment:10 Changed 10 years ago by
- Reviewers set to Karl-Dieter Crisman
- Status changed from needs_work to needs_review
This is what I meant:
sage: plot(sin(x), (x,0,2*pi), tick_locator=pi/3, tick_formatter='pi') sage: plot(1.5/(1+e^(-x)), (x,-10,10), tick_locator=[None, 1.5/4]) # shows the inflection point height!
See soon-to-be-attached patch. Thanks SO much for making this possible - my additions just make it easier for people like me who don't know matplotlib :)
Also, positive review to the parts I didn't do.
comment:11 Changed 10 years ago by
Perhaps a followup is what sort of object is a Locator? Just a list? I couldn't find this in the built-in documentation for ticker (and wasn't online when I created it). If so, then the documentation should be updated to reflect that; I still think this having the option of just a number is a good one, though I was cludgy in dealing with some of the Errors that might arise.
comment:12 follow-up: ↓ 15 Changed 10 years ago by
Two comments:
- tick_formatter should accept rational multiples of *any* symbolic constant. I think the code ought to be generalized before going into Sage. For example, I should have ticks that are rational multiples of e as well.
- I like the idea of having tick_locator be a number. I think it should be expanded to being a list (if symbolic expressions, then latexing them can give the formatters), or a function.
See http://reference.wolfram.com/mathematica/ref/Ticks.html for the equivalent mathematica feature.
comment:13 Changed 10 years ago by
- Status changed from needs_review to needs_work
comment:14 Changed 10 years ago by
I'm putting this as needs_work while we flush out a nice design.
comment:15 in reply to: ↑ 12 Changed 10 years ago by
- Description modified (diff)
Replying to jason:
Two comments:
- tick_formatter should accept rational multiples of *any* symbolic constant. I think the code ought to be generalized before going into Sage. For example, I should have ticks that are rational multiples of e as well.
- I like the idea of having tick_locator be a number. I think it should be expanded to being a list (if symbolic expressions, then latexing them can give the formatters), or a function.
These make lots of sense. It should be easy to check if the string is in symbolic.constants or something (or even accept those as input), and then of course one would have to check if there was a symbolic expression in the list, and (possibly override) use tick_formatter appropriately. After all, multiple_of_pi could be made generic to multiple_of_symbolic_constant, and then we could make a dummy function to input into FuncFormatter? or something.
comment:16 Changed 9 years ago by
- Report Upstream set to N/A
Okay, I've finally had time to return to this, and I'm almost done dealing with this. It is actually really cool!
Formatting by multiples of constants (or numbers) will be implemented, as well as locating by arbitrary lists. Note that
``LogLocator`` logarithmically ticks from min to max
could solve our log problems, though I think that would not belong on this ticket (and I think there is one for this).
comment:17 Changed 9 years ago by
- Status changed from needs_work to needs_review
comment:18 follow-up: ↓ 21 Changed 9 years ago by
- Owner changed from was to jason
- "Private" functions (like the first one) should begin with an underscore
- plot(2*x+1,(x,0,5),tick_locator=[[0,1,e,pi,sqrt(20)],2],tick_formatter="latex") looks really bad since the minor ticks do not pay attention to the major ticks
- in general, it looks really weird to have very small latex numbers on one axis while having large non-latex numbers on the other axis. I'm not sure what we can do about this.
comment:19 Changed 9 years ago by
- Status changed from needs_review to needs_work
comment:20 Changed 9 years ago by
- Owner changed from jason to kcrisman
comment:21 in reply to: ↑ 18 Changed 9 years ago by
Replying to jason:
- "Private" functions (like the first one) should begin with an underscore
You mean the multiple one? Ok.
- plot(2*x+1,(x,0,5),tick_locator=[[0,1,e,pi,sqrt(20)],2],tick_formatter="latex") looks really bad since the minor ticks do not pay attention to the major ticks
I totally agree, but I figured it was best to respond to your suggestion first, and then take ideas for how to fix this issue. Any ideas?
- in general, it looks really weird to have very small latex numbers on one axis while having large non-latex numbers on the other axis. I'm not sure what we can do about this.
I see. I don't think this is a problem - we can just make it so that "latex" is applied to both axes, if used. What do you think?
comment:22 Changed 9 years ago by
Okay, I've fixed all of these things. Note that Mma only includes major ticks for lists of ticks, so I just went with that (much easier!). The latex thing was annoying - there was a bug in sage.misc.latex.str_function:
sage: sage.misc.latex.str_function('4') '4' sage: sage.misc.latex.str_function('4.0') '\\texttt{4.0}'
At least I view this as a bug, and in any case it prevents the latexing from working properly even if the tick_locator had [0,1.0,e] rather than [0,1,e]. Unified patch coming up.
comment:23 Changed 9 years ago by
- Status changed from needs_work to needs_review
Apply only ticks, formatting, and latex patch.
comment:24 follow-up: ↓ 25 Changed 9 years ago by
- Status changed from needs_review to needs_work
Very nice now! One last thing that I saw: the examples for _multiple_... still have the problem of halfof the ticks being in latex and the other half not. For example, plot(x^2, (x, -2, 2), tick_formatter=pi) has the vertical labels in the standard font and the horizontal labels in tex.
comment:25 in reply to: ↑ 24 Changed 9 years ago by
Replying to jason:
Very nice now! One last thing that I saw: the examples for _multiple_... still have the problem of halfof the ticks being in latex and the other half not. For example, plot(x^2, (x, -2, 2), tick_formatter=pi) has the vertical labels in the standard font and the horizontal labels in tex.
Hmm, that's true. I don't know - this is more of a design decision than anything. In theory, we could latex ALL our tick labels, but that seems overkill. On the other hand, the solution to this would be kind of cludgy.
Also, I was thinking about it this morning and wondered whether it would make sense to have the "tick_locator" keyword be just "ticks" or "tick" instead. Mma using "ticks", and "tick_locator" is a little longish ("tick_formatter" makes sense, though).
Let me know if you have any ideas.
comment:26 follow-up: ↓ 27 Changed 9 years ago by
Thanks for all the work on this ticket. I'm helping my wife with a final project for an environmental science class (involving measuring "smog"), and not having tick control is devastating. Finally, for the first time, I personally really need this capability :-)
Another killer is not have a legend. Is that also easy to add via something similar? (Probably.)
comment:27 in reply to: ↑ 26 Changed 9 years ago by
comment:28 Changed 9 years ago by
Of course, you can also always use straight matplotlib to do these things right now.
comment:29 follow-ups: ↓ 30 ↓ 31 Changed 9 years ago by
And it turned out that this patch 100% totally worked to solve my problem. Yes!
Of course, it will be nice when the capabilities are doctested.
Regarding using "straight matplotlib", that would be a bit annoying, since all my plot commands are in Sage, so I have to get at the secret underlying matplotlib fig/canvas, right? Or rewrite all my code to use pylab directly. I don't want to do that "on principle".
comment:30 in reply to: ↑ 29 Changed 9 years ago by
Replying to was:
Regarding using "straight matplotlib", that would be a bit annoying, since all my plot commands are in Sage, so I have to get at the secret underlying matplotlib fig/canvas, right?
Well, it's not so secret, though, as it is exposed in a public function:
p=plot(x^{2,(x,-3,4)) figure=p.matplotlib() from matplotlib.backends.backend_agg import FigureCanvasAgg? figure.set_canvas(FigureCanvasAgg?(figure)) figure.savefig('test.png') }
(the last three lines are a bit esoteric, but they are fairly straightforward matplotlib...)
comment:31 in reply to: ↑ 29 Changed 9 years ago by
Replying to was:
And it turned out that this patch 100% totally worked to solve my problem. Yes!
Sweet! Looks great.
Of course, it will be nice when the capabilities are doctested.
Okay, that and figuring out what to do with the Latex situation are all this ticket needs, then. I can't work on this until after the first PREP workshop, but after that it's very high on my list.
comment:32 follow-up: ↓ 33 Changed 9 years ago by
From private email, in response to kcrisman's comment above:
Hmm, that's true. I don't know - this is more of a design decision than anything. In theory, we could latex ALL our tick labels, but that seems overkill. On the other hand, the solution to this would be kind of cludgy.
Also, I was thinking about it this morning and wondered whether it would make sense to have the "tick_locator" keyword be just "ticks" or "tick" instead. Mma using "ticks", and "tick_locator" is a little longish ("tick_formatter" makes sense, though).
Let me know if you have any ideas.
+1 to "ticks"
I'm not sure what to do about the latex issue. I suppose the real problem here is that matplotlib does not use compatible fonts for $1$ and just straight 1. That's a bummer. Still, in the common case (i.e., people didn't specify formatters), I think we should make it all latex or all not latex. plot(sin(x), (x,0,pi), tick_formatter=pi) is likely to be a very common case, and we shouldn't have "ugly" output for it. It looks like it might be as easy as changing the line:
1818 if y_formatter is None: 1819 y_formatter = OldScalarFormatter()
to
if y_formatter is None: y_formatter = copy(x_formatter) # maybe copy isn't needed
We could then also delete the if statement above dealing with setting both formatters to "latex" if tick_formatter="latex".
The one problem with this is that plot(..., ticks=pi) would change both the x and the y axis. Maybe we could special-case that situation.
comment:33 in reply to: ↑ 32 Changed 9 years ago by
Also, I was thinking about it this morning and wondered whether it would make sense to have the "tick_locator" keyword be just "ticks" or "tick" instead. Mma using "ticks", and "tick_locator" is a little longish ("tick_formatter" makes sense, though).
+1 to "ticks"
Ok, done.
I'm not sure what to do about the latex issue. I suppose the real problem here is that matplotlib does not use compatible fonts for $1$ and just straight 1. That's a bummer. Still, in the common case (i.e., people didn't specify formatters), I think we should make it all latex or all not latex. plot(sin(x), (x,0,pi), tick_formatter=pi) is likely to be a very common case, and we shouldn't have "ugly" output for it. It looks like it might be as easy as changing the line:
1818 if y_formatter is None: 1819 y_formatter = OldScalarFormatter()to
if y_formatter is None: y_formatter = copy(x_formatter) # maybe copy isn't neededWe could then also delete the if statement above dealing with setting both formatters to "latex" if tick_formatter="latex".
The one problem with this is that plot(..., ticks=pi) would change both the x and the y axis. Maybe we could special-case that situation.
If you note, the documentation is already quite explicit that you should not do that example in 'real life' :-) though I'll make it even more explicit. Luckily that example is also in an underscore function.
I think a better solution is that if y_formatter is None and x_formatter was in SR, then we should do y_formatter='latex'. I propose to do this via
from sage.symbolic.ring import SR if not isinstance(tick_formatter, (list, tuple)): if tick_formatter == "latex" or tick_formatter in SR: tick_formatter = (tick_formatter, "latex") else: tick_formatter = (tick_formatter, None)
I also will check for the much rarer case of plotting arcsin, so to speak. Patch coming up.
comment:34 Changed 9 years ago by
- Status changed from needs_work to needs_review
Needs review. Notice that for some reason it kept the old message - but this latest one is based on 4.4.2, no worries :-)
comment:35 Changed 9 years ago by
- Status changed from needs_review to positive_review
Looks great, applies to 4.5.2, and doctests pass in changed files. Great job!
comment:36 Changed 9 years ago by
- Merged in set to sage-4.6.alpha1
- Resolution set to fixed
- Status changed from positive_review to closed
This should be available now with #5448, but this ticket would now be to expose that functionality from matplotlib.