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 kcrisman)

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)

formatter.patch (5.6 KB) - added by jason 10 years ago.
trac_1431.patch (13.4 KB) - added by kcrisman 10 years ago.
Based on 4.2
trac_1431-ticks-and-formatting.patch (15.6 KB) - added by kcrisman 9 years ago.
Based on 4.3.5, apply only this patch to Sage library
trac_1431-ticks-and-formatting-and-latex.patch (20.2 KB) - added by kcrisman 9 years ago.
Based on 4.3.5, apply only this patch to Sage library

Download all attachments as: .zip

Change History (40)

comment:1 Changed 12 years ago by was

  • Component changed from algebraic geometry to graphics

comment:2 Changed 10 years ago by kcrisman

This should be available now with #5448, but this ticket would now be to expose that functionality from matplotlib.

Changed 10 years ago by jason

comment:3 Changed 10 years ago by jason

  • 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 jason

  • Authors set to Jason Grout

comment:5 Changed 10 years ago by kcrisman

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 jason

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 jason

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 jason

See #4529 for a related ticket---logarithmic scales.

comment:9 Changed 10 years ago by jason

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 kcrisman

  • Authors changed from Jason Grout to Jason Grout, Karl-Dieter Crisman
  • 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 kcrisman

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.

Changed 10 years ago by kcrisman

Based on 4.2

comment:12 follow-up: Changed 10 years ago by 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.

See http://reference.wolfram.com/mathematica/ref/Ticks.html for the equivalent mathematica feature.

comment:13 Changed 10 years ago by jason

  • Status changed from needs_review to needs_work

comment:14 Changed 10 years ago by jason

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 kcrisman

  • 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 kcrisman

  • 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).

Changed 9 years ago by kcrisman

Based on 4.3.5, apply only this patch to Sage library

comment:17 Changed 9 years ago by kcrisman

  • Status changed from needs_work to needs_review

comment:18 follow-up: Changed 9 years ago by jason

  • Owner changed from was to jason
  1. "Private" functions (like the first one) should begin with an underscore
  2. 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
  3. 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 jason

  • Status changed from needs_review to needs_work

comment:20 Changed 9 years ago by jason

  • Owner changed from jason to kcrisman

comment:21 in reply to: ↑ 18 Changed 9 years ago by kcrisman

Replying to jason:

  1. "Private" functions (like the first one) should begin with an underscore

You mean the multiple one? Ok.

  1. 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?

  1. 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 kcrisman

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 kcrisman

  • Status changed from needs_work to needs_review

Apply only ticks, formatting, and latex patch.

comment:24 follow-up: Changed 9 years ago by jason

  • 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 kcrisman

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: Changed 9 years ago by was

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 jason

Replying to was:

Another killer is not have a legend. Is that also easy to add via something similar? (Probably.)

See #4342

comment:28 Changed 9 years ago by jason

Of course, you can also always use straight matplotlib to do these things right now.

comment:29 follow-ups: Changed 9 years ago by was

And it turned out that this patch 100% totally worked to solve my problem. Yes!

http://tutorial.sagenb.org/home/pub/19/

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 jason

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(x2,(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 kcrisman

Replying to was:

And it turned out that this patch 100% totally worked to solve my problem. Yes!

http://tutorial.sagenb.org/home/pub/19/

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: Changed 9 years ago by jason

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 kcrisman

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 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.

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.

Changed 9 years ago by kcrisman

Based on 4.3.5, apply only this patch to Sage library

comment:34 Changed 9 years ago by kcrisman

  • 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 jason

  • 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 mpatel

  • Merged in set to sage-4.6.alpha1
  • Resolution set to fixed
  • Status changed from positive_review to closed
Note: See TracTickets for help on using tickets.