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 object1.anchors.left = object2.anchors.right. I also think that it would be awesome if lengths/coordinates could be defined as unit-aware objects, so a position/distance/length could be given in pixels, data coordinates, inches, centimeters, font-height etc. This way, the upper-right plot legend, spaced by 1.5 times the height of 'M', could be defined like
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.
@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 text.anchors.bottom represent the bottom of a text box once the height of the text within the box is computed? Are QML concepts always rectangular? For instance, if a text "element" were rotated by 45 degrees, where might one expect the text.anchors.bottom to represent?
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:
"The ideas in QML seem quite similar to the ones in edje, which is one of the libraries behind enlightenment and has been around for nearly a decade ( see http://www.enlightenment.org and http://docs.enlightenment.org/auto/edje/ ). So, it might be useful for you to look at that project since all the theming and layout in enlightenment happens through edje."
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.
For doing the layouts, it is necessary to know the size of the parts. In matplotlib the size is sometimes only known after the drawing.
Some kind of tree structure for a figure would be very helpful to collect the various constraints.
Also exact definitions of the elements are needed, e.g. does axes.left is just the left side of the axes patch or also counting in the tick labels. One would probably want to untangle the components and only use the constraints to put them together.
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.
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 (Renderer.get_text_width_height_descent()). Do you know if the t.get_extents() method you are calling retrieves the extent from the backend or is it just returning some default value until the text is actually drawn?
To allay any fears, Kiwi only has a dependency on a C++ compiler. I
wouldn't recommend Enaml for this application, but Kiwi would be right at
home.
On Sep 30, 2014 8:27 AM, "Thomas A Caswell" notifications@github.com
wrote:
Sorry I wasn't clear, I just want to use kiwi, the constraint solver.
—
Reply to this email directly or view it on GitHub #1109 (comment)
.
Am i seeing it that there is no easy way to get the base line from a text object? Because
halign(left_text.v_center, right_text.v_center) doesn't work like expected with e.g. 'g' and 'l' as text.
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.
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:
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?
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.
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 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 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.
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>
.
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>
> .
>
The notebook starts to get towards an API for lining up axes using the layout manager. See the section with constrained_layout(fig, axs), where axs is a list of axes (in the figure, I hope) that you want to be constrained together. They will stay within their gridspec, and constraining them together will give them all the same label, ticklabel, title - accommodating margins.
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 figure (i.e. fig.contrain_layout()) can be up for discussion once the rest of the ideas are fleshed out.
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.
Activity
pwuertz commented on Sep 1, 2012
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 object1.anchors.left = object2.anchors.right. I also think that it would be awesome if lengths/coordinates could be defined as unit-aware objects, so a position/distance/length could be given in pixels, data coordinates, inches, centimeters, font-height etc. This way, the upper-right plot legend, spaced by 1.5 times the height of 'M', could be defined like
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?
pelson commented on Sep 1, 2012
@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 text.anchors.bottom represent the bottom of a text box once the height of the text within the box is computed? Are QML concepts always rectangular? For instance, if a text "element" were rotated by 45 degrees, where might one expect the text.anchors.bottom to represent?
pwuertz commented on Sep 3, 2012
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.
ppurka commented on Oct 19, 2012
I looked up QML after finding it mentioned here. So, repeating what I commented on in #1415:
"The ideas in QML seem quite similar to the ones in edje, which is one of the libraries behind enlightenment and has been around for nearly a decade ( see http://www.enlightenment.org and http://docs.enlightenment.org/auto/edje/ ). So, it might be useful for you to look at that project since all the theming and layout in enlightenment happens through edje."
pwuertz commented on Oct 19, 2012
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..
Tillsten commented on Sep 30, 2014
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.
tacaswell commented on Sep 30, 2014
I talked with @sccolbert (the author of kiwi and enamal) over the weekend at am 👍 on making use of it.
efiring commented on Sep 30, 2014
@tacaswell are you referring to using enaml in mpl? It looks like it pulls in heavy dependencies, so as usual, I'm skeptical.
tacaswell commented on Sep 30, 2014
Sorry I wasn't clear, I just want to use kiwi, the constraint solver.
Tillsten commented on Sep 30, 2014
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?
pwuertz commented on Sep 30, 2014
Just a guss: The correct text-size can only be determined by the rendering backend (Renderer.get_text_width_height_descent()). Do you know if the t.get_extents() method you are calling retrieves the extent from the backend or is it just returning some default value until the text is actually drawn?
sccolbert commented on Oct 1, 2014
To allay any fears, Kiwi only has a dependency on a C++ compiler. I
wouldn't recommend Enaml for this application, but Kiwi would be right at
home.
On Sep 30, 2014 8:27 AM, "Thomas A Caswell" notifications@github.com
wrote:
Tillsten commented on Oct 6, 2014
Am i seeing it that there is no easy way to get the base line from a text object? Because
halign(left_text.v_center, right_text.v_center) doesn't work like expected with e.g. 'g' and 'l' as text.
sccolbert commented on Oct 8, 2014
A pure Python implementation of Cassowary is going to be painfully slow. Any particular reason you don't want to use Kiwi for this?
Tillsten commented on Oct 8, 2014
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.
28 remaining items
jklymak commented on Aug 4, 2017
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.
jklymak commented on Aug 4, 2017
I think I understand what is going on well enough via @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?
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
WeatherGod commented on Aug 4, 2017
jklymak commented on Aug 4, 2017
@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 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.
WeatherGod commented on Aug 4, 2017
WeatherGod commented on Aug 4, 2017
anntzer commented on Aug 5, 2017
(going to have on-and-off internet access in the coming days, so don't wait for me :-))
jklymak commented on Aug 10, 2017
Progress report: I have made a new package LayoutBox that implements a LayoutBox class and provides a bunch of helper functions. Heavily based on @Tillsten . Not properly documented yet etc.
https://github.com/jklymak/mplconstrainedlayout/blob/master/LayoutBox.py
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 constrained_layout(fig, axs), where axs is a list of axes (in the figure, I hope) that you want to be constrained together. They will stay within their gridspec, and constraining them together will give them all the same label, ticklabel, title - accommodating margins.
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 figure (i.e. fig.contrain_layout()) can be up for discussion once the rest of the ideas are fleshed out.
Any comments/criticisms most welcome.
jklymak commented on Aug 14, 2017
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.
@tacaswell
jklymak commented on Aug 23, 2017
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.
jklymak commented on Aug 23, 2017
Update: See PR #9082 (WIP)
dstansby commented on Feb 13, 2018
Closing since #9082 went into 2.2