Improving SmartFigure indexing #646
Replies: 3 comments 9 replies
-
|
@mamar828 @charlesantoineparent3 @Mykola-P I'd love your input on this if/when you have the time! Sorry for the ridiculously long text haha |
Beta Was this translation helpful? Give feedback.
-
|
Yeah, you're kind of converging towards an idea I started to have more and more haha. I was also thinking that it could be useful to always create nested However, I didn't implement it that way because I felt it would kind of complicate default attributes. Say you create a 2x2 Another example would be: you create a 2x2 At least maybe something that could help would be to implement a helper method that converts a subplot to a As for the indexing, I understand your points about being able to slice incoherently and then having to know the exact layout of the I also agree with your suggestion of being able to "click" in a specific subplot even though it spans multiple rows/columns. This could be implemented by tweaking the way elements are added and removed from the Thank you for starting this discussion: I was probably going to start one about this very subject one day! |
Beta Was this translation helpful? Give feedback.
-
|
I agree with your assessment that it would be better to use "default" for both inheritance and style management. Using types like As for the parameter lists, their goal is to allow to modify parameters for individual subplots without having to create a For the other parameters, I think we could use a |
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
-
Hey everyone,
I've been thinking about the SmartFigure quite a bit, especially the indexing API. This a great shortcut to organize and access sub figures and Plottables. And while I think the idea is great, I do think there are some things that could benefit from a bit of polishing or rethinking. This relates in part to the following issue in @mamar828 's fork:
This issue is one symptom of a larger problem, which, if I had to summarize it in one sentence, would be that the indexing with SmartFigures can lead to unintuitive behaviour. See the following example which I adapted from the docs:
Here, we have a pretty simple layout with 2 figures in the top row and one wide figure in the bottom row. However, the indexing returns a wide range of things depending on how elements were inserting. This is something which I find a bit annoying. I view the different ways of setting up a SmartFigure and of adding elements as just that: different ways of doing the same thing, to accommodate different workflows. But here, clearly, these different ways don't really end up doing the same thing. Adding a sub figure via
parent.elements = [subfigure, None]vs viaparent.elements = [[curve1, curve2], None]leads to different behaviours of the indexing calls (even though the figures produced look and are the same).Criterion A: Different APIs to accomplish the same thing should lead to indistinguishable results.
Another angle from which to view this issue is type checking. We haven't put much emphasis on types as of now (just try running
ty checkon the repo to see how much work there is to do in that department). But ideally, I think it should probably be the case that all first level indexing of a SmartFigure should return the same type. That type could be a Plottable if the SmartFigure is not nested (contains only Plottables), or a SmartFigure, if's a parent SmartFigure. But for a given SmartFigure,type(fig[0, 0]) == type(fig[0, 1] == type(fig[1, 0]) ...should be true. This would make things more uniform. Maybe one solution which would solve both this issue and the previous one is to dynamically create a sub figure when users pass in a list of Plottables? This would make passing in a list of Plottables just a shortcut when you're too lazy to create a whole sub figure yourself (but it actually is created for you behind the scenes, and you can access it using indexing).Criterion B: Indexing one level deep should return same type across all indices (whether that's Plottables or SmartFigures)
There's another issue which is that we're using a tool (indexing) that's too powerful and flexible for what we need (ex: I could index a completely nonsensical slice which intersects through multiple figures and doesn't include any complete figure). More generally, the question we need to answer is this: What does indexing on a SmartFigure mean? I think we can view it as either a "select" tool or a "pointer" tool, and both have different implications, both for assignment (
parent[0, 1] = fig) and for accessing (parent[0, 1].set_grid(...)).All a pointer tool can do is click. This would mean no slices, only put in the coordinates of the cell you want to access/assign to. For accessing, this would mean that if a sub figure spans the two bottom rows of a 2x2 grid, both
parent[1,0]andparent[1,1]will return that sub figure. Clicking on a cell returns whatever figure happens to be on that cell, even if the figure is bigger than just that cell. For assignment, we'd need another way to specify the span of the sub figure we're adding. You'd callparent[1,0] = new_sub_fig.set_cols_in_parent(2)or something like that (might be a better way to do it, considering that this won't work withparent[1,0] = [my_curve]). In any case, you'd "click" on the top left cell of the span you want your sub figure to occupy, and specify the span some other way.A select tool only works with ranges. This is the current way things work. Assign with the exact range where you want the sub figure to be, access with the exact range where the sub figure is. It's consistent. But it comes with the following downsides: additional complexity (understanding ranges/slices), needing to know the exact slice of the figure you're looking for, and ambiguity in the case of nonsensical slices (what if a slice contains only part of a subfigure? two whole figures? one and a half figures?).
In Rust, there's this common saying: “Make invalid states unrepresentable”. This means designing your API so users can’t even construct a value that breaks your rules, instead of allowing it and checking later. For example, use two types like UnpaidPayment(amount) and PaidPayment(amount, paid_at) rather than one Payment(status, paid_at) where status="paid" but paid_at=None is possible in the database but impossible in reality. The pointer option seems to me like it's closer to this ideal than the select option, but it still has the issue of how to specify spans.
I'm legitimately unsure what the solution is here, or if there even is any. There may be a clever way to combine the two approaches and get the best of both worlds? But then again, we'd lose the clear intuition of click vs select.
Criterion C: Make slices make more sense? Somehow??? Idk
I think at least part of the solution to all this is:
.elementsor something similar:This would solve the @mamar828's += issue. You can call
standalone_figure += my_curveorstandalone_figure.elements += my_curve. For a parent figure, you can callparent[0,1] += my_curvebecause the left side evaluates to a SmartFigure object. It would also solve Criterion A and Criterion B. It still leaves open the question of what to do about Criterion C.This is all extremely untested and brainstormy, so if you disagree with anything, have other justifications for why things are the way they currently are, or have any other ideas, please comment them! I specifically started a discussion on this topic because my confidence in everything I wrote above is rather weak and I want other people's impressions.
Beta Was this translation helpful? Give feedback.
All reactions