Skip to content

rename fig.draw_no_output #20001

Closed
Closed

Description

@jklymak

#19968 introduced a no-output draw fig.draw_no_output() which does what fig.canvas.draw() does, but with presumably less side effects. This change should be better reflected in the code base, tests, and docs.

Good first issue, because there isn't really anything tricky here. If someone takes it on, it may be preferable to take it on in stages so it is not a megalithic change.

Activity

  1. timhoffm commented on Apr 17, 2021

    @timhoffm
    Member

    I agree with #19968 (review) that we should try to think of a better name before rolling this out everywhere.

  2. tacaswell commented on Apr 17, 2021

    @tacaswell
    Member

    Sorry if I jumped the gun on merging with this name

    xref #19765 (comment)

  3. jklymak commented on Apr 17, 2021

    @jklymak
    MemberAuthor

    I await a better name with bated breath!

  4. timhoffm commented on Apr 18, 2021

    @timhoffm
    Member

    Coming from #19968 (review)

    draw has basically grown to do "mean" two things:

    1. crawl the artist tree
    2. actually do things involving the renderer

    We'll get to the name when we can name those two things. I'm not quite sure what exactly 1 actually does, but that's the name we need. This needs somebody who knows exactly what is done internally. Might be it's layout_artists. Then we could say draw = layout_artists + render.

    If nobody steps up, we can try to find the name jointly in the next dev call.

  5. jklymak commented on Apr 18, 2021

    @jklymak
    MemberAuthor

    The routine walks the artist tree and calls the draw method on every artist. It just stops short of outputting the results to the renderer.

  6. story645 commented on Apr 18, 2021

    @story645
    Member

    I call the all put together but not rendered thing the graphic, since in the visualization literature is a graphic whether it's theoretical or rendered to an image. Probably stole the terminology from Grammar of Graphics
    image
    which is probably a short hand for graphical design (from "A Presentation Tool" by Jock Mackinlay)
    image

  7. jklymak commented on Apr 18, 2021

    @jklymak
    MemberAuthor

    I think we are after a verb, not a noun, and I think we call those things "Artists".

  8. tacaswell commented on Apr 19, 2021

    @tacaswell
    Member

    I'm not quite sure what exactly 1 actually does

    The way this is implemented is that we do fig.draw(render_with_no_op_methods). Thus, everything on the Artist side of the draw (resolving anything lazy (limits, some RBGA mapping that we cache, some truncation in Line2D, resolve any "relative to other artist" transfroms), computing how big the text will be, sort where ticks are, etc) will happen. In the normal course of events, the Artist.draw methods are responsible for boiling down everything it knows from the user and using that information to set up GraphicConexts and call the renderer.draw_XYZ methods (see https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.RendererBase it is out of these primitives (which map closely to the SVG primitives) that we make everything). Exactly what gets done in draw_xyz is up to the backend (e.g. in svg this produces text, in the case of AGG it composites another layer onto its internal buffer) and in the draw_no_output case it does nothing.

    Concretely, if we look at Axis.draw

    @martist.allow_rasterization
    def draw(self, renderer, *args, **kwargs):
    # docstring inherited
    if not self.get_visible():
    return
    renderer.open_group(__name__, gid=self.get_gid())
    ticks_to_draw = self._update_ticks()
    ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
    renderer)
    for tick in ticks_to_draw:
    tick.draw(renderer)
    # scale up the axis label box to also find the neighbors, not
    # just the tick labels that actually overlap note we need a
    # *copy* of the axis label box because we don't want to scale
    # the actual bbox
    self._update_label_position(renderer)
    self.label.draw(renderer)
    self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
    self.offsetText.set_text(self.major.formatter.get_offset())
    self.offsetText.draw(renderer)
    renderer.close_group(__name__)
    self.stale = False
    we see that it:

    • does a short-circuit check
    • opens a group (matters in some of the vector backends)
    • updates the ticks (self._update_ticks() is where we sort out who is visible etc and all of the logic to re-use ticks lives)
    • loop over the ticks to draw and call their draws
    • sorts out where the axis label should be
    • asks the Text object that is the label to please draw itself
    • does the same song/dance with the offset text (this is for when we say '+1e10')
    • close the group and mark it self as not-stale

    At no point in here did we actually use the renderer, we have to go a couple of more level down through

    @martist.allow_rasterization
    def draw(self, renderer):
    if not self.get_visible():
    self.stale = False
    return
    renderer.open_group(self.__name__, gid=self.get_gid())
    for artist in [self.gridline, self.tick1line, self.tick2line,
    self.label1, self.label2]:
    artist.draw(renderer)
    renderer.close_group(self.__name__)
    self.stale = False
    to
    if textobj.get_usetex():
    textrenderer.draw_tex(gc, x, y, clean_line,
    textobj._fontproperties, angle,
    mtext=mtext)
    else:
    textrenderer.draw_text(gc, x, y, clean_line,
    textobj._fontproperties, angle,
    ismath=ismath, mtext=mtext)
    or
    gc = renderer.new_gc()
    self._set_gc_clip(gc)
    gc.set_url(self.get_url())
    lc_rgba = mcolors.to_rgba(self._color, self._alpha)
    gc.set_foreground(lc_rgba, isRGBA=True)
    gc.set_antialiased(self._antialiased)
    gc.set_linewidth(self._linewidth)
    if self.is_dashed():
    cap = self._dashcapstyle
    join = self._dashjoinstyle
    else:
    cap = self._solidcapstyle
    join = self._solidjoinstyle
    gc.set_joinstyle(join)
    gc.set_capstyle(cap)
    gc.set_snap(self.get_snap())
    if self.get_sketch_params() is not None:
    gc.set_sketch_params(*self.get_sketch_params())
    gc.set_dashes(self._dashOffset, self._dashSeq)
    renderer.draw_path(gc, tpath, affine.frozen())
    gc.restore()
    to find the renderer methods in use.

    It should also be noted, that when we talk about doing stuff on a GPU we usually get stuck on "we do too much in Python", it is this aspect of doing lots and lots of work in the Artist.draw method. On the other hand, this design is also what lets us support the variety of backends that we do!

    This suggest draw_no_render is a better name?

    I suspect the historical path we took to get to the collection of draw methods is that the very first version of Matplotlib was a gtk application gtk uses draw in this context (e.g. https://developer.gnome.org/gtkmm-tutorial/stable/sec-cairo-drawing-lines.html.en). This is pretty thematically consistent with GUI toolkits for "the code that runs to sort out what color the pixels on the screen should be" as QT uses "paint" in this context.


    In the first diagram what GoG calls "Graphic" is what we call "Artist" and in the second the construction of the Artists is what is labeled "Graphical Design". In both cases we are worried about details inside of the "Render" or "render" parts so I do not think either is going to provide us useful naming guidance in this case.

  9. brunobeltran commented on Apr 22, 2021

    @brunobeltran
    Contributor

    Thus, everything on the Artist side of the draw (resolving anything lazy (limits, some RBGA mapping that we cache, some truncation in Line2D, resolve any "relative to other artist" transfroms), computing how big the text will be, sort where ticks are, etc) will happen.

    This is exactly the thing which Eric and I are hoping we find a "name" for.

    In the normal course of events, the Artist.draw methods are responsible for

    1. boiling down everything it knows from the user and
    2. using that information to set up GraphicConexts and call the renderer.draw_XYZ methods

    (numbering added by me). I think from an API design perspective, the fact that we implement draw_no_output by (basically) setting a flag that lets us skip step (2), doesn't matter, because most users don't and shouldn't have to know that.

    I approved the existing name because I don't have a better suggestion. But if we do have a good name for (1), we should use that instead of "draw".

  10. brunobeltran commented on Apr 22, 2021

    @brunobeltran
    Contributor

    layout_artists. Then we could say draw = layout_artists + render.

    I can definitely get behind this.

    The routine walks the artist tree and calls the draw method on every artist. It just stops short of outputting the results to the renderer.

    but I don't think the implementation should affect the naming. I think we can still say fig.draw = fig.layout + fig.render even if that's not "true" at an implementation level, it's definitely true semantically.

    "layout" might not be "all encompassing" enough, but is basically what I was hoping for.

  11. brunobeltran commented on Apr 22, 2021

    @brunobeltran
    Contributor

    EDIT: sorry for the close/open. My mouse hates me.

  12. 1 remaining item

  13. jklymak commented on Apr 22, 2021

    @jklymak
    MemberAuthor

    I'm not sure it is terribly practical to change the nomenclature all the way back to Artist. That seems a lot of churn for little practical gain.

    Again, I'll remind folks were are trying to come up with a replacement name for fig.canvas.draw which has served the purpose for 15 years despite not containing any indication that it did not actually produce an output. That the replacement should also contain the verb draw makes perfect sense to me a) because that is the historical verb and b) because that is what it is doing, except it stops at the point of making an output. I think of it as drawing something in your favourite graphics program, but never sending it to the printer.

    I suppose the verb "compose" is really what is happening here, and maybe fig.compose_no_output would be OK, but given we call this "draw" everywhere else, I'm still not particularly convinced the slight difference in verb is that helpful.

  14. changed the title [-]fig.draw_no_output should replace many (most) instances of fig.canvas.draw[/-] [+]rename fig.draw_no_output[/+] on Apr 23, 2021
  15. added
    API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …
    and removed
    🌱 Good first issueOpen a pull request against these issues if there are no active ones!
    on Apr 23, 2021
  16. added this to the v3.5.0 milestone on Apr 23, 2021
  17. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Apr 23, 2021
  18. tacaswell commented on Apr 23, 2021

    @tacaswell
    Member

    I renamed and re-labeled this issue to reflect the discussion we are actually having here ;)

  19. timhoffm commented on Apr 23, 2021

    @timhoffm
    Member

    I like compose(). _no_output postfix is unneeded there as composing does not have a notion of output in the first place.

    I agree with @brunobeltran that the naming here does not need to reflect implementation (or low-level naming).

    At the semantic top level we'd have "drawing is composing + renderding". The low-level implementation can only draw. The realization of an isolated compose() is done by drawing with a null-renderer.

  20. QuLogic commented on Aug 17, 2021

    @QuLogic
    Member

    This is going out in 3.5 soon; does anyone have any new/final opinions on renaming?

  21. timhoffm commented on Aug 26, 2021

    @timhoffm
    Member

    Naming decision on the dev call: draw_without_rendering().

  22. jklymak commented on Aug 27, 2021

    @jklymak
    MemberAuthor

    I still vote for draw_no_output, so if someone who prefers draw_without_rendering could put the PR through, they should do that.

  23. added a commit that references this issue on Aug 28, 2021
    5821a24
  24. added a commit that references this issue on Oct 20, 2021
    be6c24c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions