-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
MEP for a matplotlib geometry manager #1109
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
Comments
I love programming with Qt4, so here are two considerations concerning layout handling based on that toolkit. Graphical user interfaces usually consist of a bunch of widgets that are hierarchically organized by layout managers. The basic layout classes implement Vertical, Horizontal and Grid alignment. The widgets include attributes like spacing, padding or scaling policies the layout managers can take into account. Although this works perfectly for applications this model probably isn't flexible enough for plotting figures. For example, there is no layout manager equivalent for a legend being placed on top of a axes widget and being aligned to some edge. I don't know if you have had the chance of looking into QML, but I think this way of aligning objects could be a really powerful solution! Every object has anchor attributes like top, bottom, left, right, center. Aligning two objects is as simple as defining an expression
QML interprets these statements as bindings that are dynamically evaluated on demand, so the python equivalent of the right hand side would be a lambda function. Such alignments determine the position, width and height of an object unless the object defines a fixed value for one or multiple of these attributes. Solving the dependencies of multiple objects referencing each other is a little bit tricky.. but I think I already got the python code for that task! In one of my projects I implemented a data table where a user can define an equation for a table cell that may depend on the values of other cells, just like Excel :). Using the python ast-module I figure out which objects are referenced in an expression and build a dependency tree. This tree is then solved so that the cell values are calculated in the right order. Faulty circular dependencies are detected and reported back to the user. Opinions? |
@pwuertz: I think this is a great start to this PR. Thank you. I wonder if you could share any experience or wisdom about text handling with this anchor mechanism. Would |
In QML rotations are applied at object level. This means that any child objects of a rotated text, which inherit the transformation, can be correctly aligned to its parent in the rotated space. This also implies that there is no meaningful anchoring definition for siblings or parent objects living in another coordinate system. Keeping such objects aligned is still possible since you can just define an equation for the x,y,width,height properties. It's just a matter of definition. So if the alignment of a rotated text in a non-rotated environment is the most frequent usecase one could very well define that a rotated text is a non-rotated object that encloses the text. Another solution could be a the definition of a group object that adapts in width and height so it encloses all children and aligns them as whole. |
I looked up QML after finding it mentioned here. So, repeating what I commented on in #1415: |
Yes, the ideas are pretty much the same. The real task however is to figure out how to apply this concept to matplotlib so all the issues and features mentioned above can be satisfied. When I got a little bit more time I'll try to stitch together a simple demo implementation to show you what I had in mind. Might be a matter of weeks though.. |
I played around with the pure python cassowary implementation and matplotlib, doing the most simple thing to see what can be done (https://gist.github.com/cee3373e3967c84be496). Even this very simple example - using the solver to align a y label - is showing some of the requirements of a real geometry manager.
If anyone wants to use a constraints based geometry manager, i suggest to take a deep look at enaml, which uses cassowary for gui-layout and has a lot of useable code. |
I talked with @sccolbert (the author of kiwi and enamal) over the weekend at am 👍 on making use of it. |
@tacaswell are you referring to using enaml in mpl? It looks like it pulls in heavy dependencies, so as usual, I'm skeptical. |
Sorry I wasn't clear, I just want to use kiwi, the constraint solver. |
There is a slightly more advanced prototype at https://gist.github.com/Tillsten/cee3373e3967c84be496. The Problem at the moment is that i am note able to get the right size of text artists in figure space, is there a way? |
Just a guss: The correct text-size can only be determined by the rendering backend ( |
To allay any fears, Kiwi only has a dependency on a C++ compiler. I
|
Am i seeing it that there is no easy way to get the base line from a text object? Because |
A pure Python implementation of Cassowary is going to be painfully slow. Any particular reason you don't want to use Kiwi for this? |
scolbert: It is fast enought for a proof of concept and has better documentation which was helpful for me get an better idea of cassowary, but if this ever get out of the poc-stage, changing it to kiwi will be quite simple i think. |
Btw i am using an already existing package called cassowary, if this wasn't clear. |
Yep, I saw that. I looked at cassowary, it's basically a port of the original Cassowary C++ code to Python. Given that Kiwi is 40x faster than the original algorithm, I don't think the pure Python version will be suitable for anything outside of proof-of-concept. The Kiwi API is very simple: In [1]: import kiwisolver as kiwi
In [2]: x = kiwi.Variable('x')
In [3]: y = kiwi.Variable('y')
In [4]: c1 = x + y == 20
In [5]: c2 = y <= -10
In [6]: s = kiwi.Solver()
In [7]: s.addConstraint(c1)
In [8]: s.addConstraint(c2)
In [9]: s.updateVariables()
In [10]: print x.value()
30.0
In [11]: print y.value()
-10.0
In [12]: s.addEditVariable(x, 'strong')
In [13]: s.suggestValue(x, 44)
In [14]: s.updateVariables()
In [15]: print x.value()
44.0
In [16]: print y.value()
-24.0
In [17]: s.suggestValue(x, 14)
In [18]: s.updateVariables()
In [19]: print x.value()
30.0
In [20]: print y.value()
-10.0 |
@sccolbert Playing around with kiwi again, the follwing questions pop up:
|
@Tillsten The combination of If your particular problem doesn't have this type of setup, that's okay. There are lots of problems which don't really have "inputs" of which to speak, and the system is just solved once in its entirety. In that case, you can just ignore edit variables and You can change the strength of a constraint expression using the User code will typically always use a symbolic strength: |
Thanks a lot! I even tried ORing, but i forgot the braces around the constraint. There is now a atleast somewhat working very basic prototype at https://github.com/Tillsten/MplLayouter. At the moment it uses its own methods for adding labels and titles, any intregation would requiere to subclass Axes and overwrite the corresponding methods. As most here will understand, i don't want to touch Axes at the moment. Still a open issuse is the handling of ticklabels: right now u reuse the approach of tight_layout, that means drawing the axes checking and adjust the size afterwards. But this makes the handling of axes alignment more difficult than necessery. I hope that i can still work around it or maybe distangle the ticklabels from the rest. Also while it is possible to get the text extent, i still don't know how to get the text baseline. Any help or comments are welcome. |
To see the (boring) example, just run the python file. Needs kiwisolver (avaible via conda). The only nice thing which can be seen is that the top labels are automatically aligned with resepect to each other, regardless of titles or the size of the ticklabels. API, the figure layout itself and the state handling are still quite basic. Here on problay could copy some things directly from enaml. |
Concerning text baseline, see my earlier comment (hint to |
Somewhat modifed version of @Tillsten layout manager that tries to take into account the tick and ticklabel sizes. It places the axes elements "somewhere" and then looks at the difference between the Its fragile to backends. It mostly seems to work for Qt5Agg. It doesn't work for |
You may want to give a try with #8771 -- not so much because of the cairo rendering, but because that PR tries to cleanly separate the rendering part (by Agg) from the windowing part (by Qt or whatever GUI toolkit you're using). |
@anntzer That has quite a few dependencies that don't seem trivial to install on OS X, unless I'm missing something... |
What I am still not following is how people think this would work w/o having made all the plot commands you are interested in making before calling the layout. @Tillsten example calls I will also say that the constraints thing is cool, but I'm not 100% sold that its the easiest way to do this problem. Just consider the width dimension in the example above. You know dx for the ylabel, and for the tickmarks+labels, so dx for the axis is determined. You can constrain it to match another axis, and I guess thats a bit easier with the constraints, but is it far easier? I'm not sure. It still seems to me that |
To use cairo, possibly. But the refactor of Qt5Agg doesn't have any dependency (beyond qt5 and agg which are already present). |
@anntzer As usual, I was missing something |
OK, this still has trouble printing.
If I use backend
So, printing is a bit of an issue. |
|
https://github.com/jklymak/mplconstrainedlayout now has a colorbar implemented as an |
Printing issue was due to the fact that @Tillsten was placing the text using pixel rather than figure units. Caused a bunch of label funkiness, but now it all works well. Modified the code to do the constraint in figure units as well. |
I think I understand what is going on well enough via @Tillsten example to make progress. Now Taking this back a step, some questions:
So what are the strong reasons for getting more granular than an EDIT: I guess folks could want to line up y/x labels between axes and that could be an extra constraint on the labels. That doesn't strike me as hugely useful, but I could be convinced otherwise. I'm still after some guidance on how this gets executed. When in matplotlb does the constraint solver get executed? I have lots of API questions, but I think guidance on if an axes-level constraint system would be good enough to start would help make that task a lot easier. Just pinging a few devs/interested folks (sorry if I missed someone) : @anntzer @tacaswell @pelson @pwuertz @WeatherGod @efiring |
Possible use-cases (more of a devil's advocate, and might not be
appropriate for a constraints solver anyway):
- automatic placement of legend within the plot box (we have code that
does this a bit right now)
- automatic placement of contour labels (via clabel(), which isn't great
in its current form)
Another possibility is making the constraints solver available to the
end-user for situations like automatically adjusting text and annotations
so they don't overlap each other. This is a situation I am running into
right now with plotting wildfires on a map and labeling them with names. As
I zoom out, the labels become useless.
…On Fri, Aug 4, 2017 at 10:08 AM, Jody Klymak ***@***.***> wrote:
I think I understand what is going on well enough via @Tillsten
<https://github.com/tillsten> example to make progress. Now Taking this
back a step, some questions:
ax.get_tightbbox(renderer) gives the bounding box for the whole axis
including the x and y labels and title. The code for placing these items is
quite mature, and to my mind does the "right" thing, and is deterministic
(i.e. the xlabel goes to the left/right of the ticks. )
So what are the strong reasons for getting more granular than an axes
object (spines+all labels) in the layout manager? The only one I've
personally come across is allowing right-justification of tick labels that
are on the right hand side of an axis. The issues referenced above don't
seem to pertain to anything that can't be handled at a level above an axes
object: i.e. lining up axes or their spines, making sure they fit on the
canvas, and adding various parasitic axes (colorbar, legend, suptitle). For
a first pass, doing things at the axis level (and higher level objects like
suptitles, colorbars, legends) would be far easier than going all the way
down to the artist level. What am I missing?
I'm still after some guidance on how this gets executed. When in matplotlb
does the constraint solver get executed?
I have lots of API questions, but I think guidance on if an axes-level
constraint system would be good enough to start would help make that task a
lot easier.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#1109 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AARy-MJzzojfNrMvHoB0XFa6VZli5exGks5sUyXqgaJpZM4AHX9l>
.
|
@WeatherGod Cool - both those cases are inside axes. Would we ever constrain from the inside out? As in the size of the axis would change due to what is inside it? Right now we certainly don't and something like the toy or If we are OK with the axes size being set by the outside constraints (like it is now), then plotting within the axis could have its own layout manager that constrains elements just inside the axis. So what is needed is a |
That makes sense. Probably the only complication I can think of is our
imshow() behavior, which does some weird feedback to the axes aspect ratio.
…On Fri, Aug 4, 2017 at 1:20 PM, Jody Klymak ***@***.***> wrote:
@WeatherGod <https://github.com/weathergod> Cool - both those cases are
inside axes. Would we ever constrain from the inside out? As in the size of
the axis would change due to what is inside it? Right now we certainly
don't and something like the toy or tight_layout only constrain and axis
size by elements *outside* the axis like how big tick labels and other
labels are.
If we are OK with the axes size being set by the outside constraints (like
it is now), then plotting within the axis could have its own layout manager
that constrains elements just inside the axis.
So what is needed is a LayoutBox class (similar to @Tillsten
<https://github.com/tillsten> Box class) that defines most common layout
desires *and* gives access to the underlying constraints. A group of
LayoutBox items would share a kiwi.Solver() and determine layout for that
group. Then you have a high-level layout group that attempts to lay out the
axes, suptitles, externally placed legends, colorbars etc. If desired,
inside each axis you could have a separate layout group that attempts to
layout axes elements. Etc. The advantage of this is that the high-level
axis layout could be implemented w/o implementing the inside axis layout.
If someone wanted to tackle in-axis layout, they could have at it.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1109 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AARy-PzSmDB1A45dCLOmOaa96biWEozwks5sU1LxgaJpZM4AHX9l>
.
|
oooh, just thought of another thing -- spine placements
https://matplotlib.org/examples/pylab_examples/spine_placement_demo.html
…On Fri, Aug 4, 2017 at 2:47 PM, Benjamin Root ***@***.***> wrote:
That makes sense. Probably the only complication I can think of is our
imshow() behavior, which does some weird feedback to the axes aspect ratio.
On Fri, Aug 4, 2017 at 1:20 PM, Jody Klymak ***@***.***>
wrote:
> @WeatherGod <https://github.com/weathergod> Cool - both those cases are
> inside axes. Would we ever constrain from the inside out? As in the size of
> the axis would change due to what is inside it? Right now we certainly
> don't and something like the toy or tight_layout only constrain and axis
> size by elements *outside* the axis like how big tick labels and other
> labels are.
>
> If we are OK with the axes size being set by the outside constraints
> (like it is now), then plotting within the axis could have its own layout
> manager that constrains elements just inside the axis.
>
> So what is needed is a LayoutBox class (similar to @Tillsten
> <https://github.com/tillsten> Box class) that defines most common layout
> desires *and* gives access to the underlying constraints. A group of
> LayoutBox items would share a kiwi.Solver() and determine layout for
> that group. Then you have a high-level layout group that attempts to lay
> out the axes, suptitles, externally placed legends, colorbars etc. If
> desired, inside each axis you could have a separate layout group that
> attempts to layout axes elements. Etc. The advantage of this is that the
> high-level axis layout could be implemented w/o implementing the inside
> axis layout. If someone wanted to tackle in-axis layout, they could have at
> it.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#1109 (comment)>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AARy-PzSmDB1A45dCLOmOaa96biWEozwks5sU1LxgaJpZM4AHX9l>
> .
>
|
(going to have on-and-off internet access in the coming days, so don't wait for me :-)) |
Progress report: I have made a new package I've also made a test notebook for this class: https://github.com/jklymak/mplconstrainedlayout/blob/master/TestLayoutBox.ipynb The notebook starts to get towards an API for lining up axes using the layout manager. See the section with I need to work on implementing nested gridspecs, but I think this is moving in the right direction. Whether this happens automatically at some point in the future, or gets added as a bolt-on method to Any comments/criticisms most welcome. |
A draft of a MEP (in notebook format) https://github.com/jklymak/mplconstrainedlayout/blob/master/DraftMEP.ipynb Comments very welcome. As you can see, the scope here is pretty modest, and maybe not quite what folks were thinking, so any suggestions would be great. Of course the same ideas could be used deeper inside an "axes", but I kept the scope "axes and larger" for now. I've managed to implement most of this already, but not very cleanly yet. If there is positive feedback I'll keep going. If not, I'm happy to let someone else run with the ball. |
Update: https://github.com/jklymak/matplotlib/tree/constrainedlayout is a fork of relatively recent master that is close to pull release. Right now all it does it "constrained_layout" on subplots, (nested) gridspecs, and colorbars. Todo is to add suptitle support and maybe legends. Note that right now there are two API changes:
I have tests, but I haven't put them in proper test format yet. |
Update: See PR #9082 (WIP) |
Closing since #9082 went into |
Write a MEP for a matplotlib figure geometry manager.
There is a lot of experience in the matplotlib community with requirements & ideas. This would be a good place to collate before a MEP gets written.
The text was updated successfully, but these errors were encountered: