-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Layout grids #2399
Layout grids #2399
Conversation
@@ -33,11 +33,6 @@ | |||
"xaxis": { | |||
"type": "category" | |||
}, | |||
"barmode": "group", | |||
"categories": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this was supposed to be xaxis.categories
? Anyway it was ignored so 🔪
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mock is ancient. Way older than the axis.categories
attribute. This must be a typo. 👍
// 200 was not robust for me (AJ), 300 seems to be. | ||
return delay(300)(); | ||
// 300 was not robust for me (AJ), 500 seems to be. | ||
return delay(500)(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I be worried that I've had to increase this delay twice now? I just ignored this test for a while, but it had been failing for me locally for at least the last 6 months...
otherOpts: ['dflt', 'freeLength'], | ||
// set dimensions=2 for a 2D array | ||
// `items` may be a single object instead of an array, in which case | ||
// `freeLength` must be true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed 1D and 2D arbitrary-length info_array
for specifying axes and/or subplots for the grid. I didn't actually end up using coerce
with these, since there's validation involved where allowed values depend on both the available axes/subplots and on earlier elements in the same array; but it's still important for documentation, and for Plotly.react
it's important that these are marked as info_array
and not data_array
.
Anyway, they work, in case we find a need in the future... tested below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ones are for function arguments, and we know that the functions being called have a maximum of 3 arguments. The [{}, {}, {}]
form ensures we never go past three args. I suppose it probably wouldn't hurt to change it, but I didn't feel like taking that risk as I'm not sure how well tested the edge cases are for these components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point here about args
being at-most a three-item array. Let's keep that as it 👌
But this has me thinking: should we change attributes like ticktext
and tickvals
to free-length info_array
instead of their current data_array
val type? That way, we could get rid of this ugly hack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we change attributes like
ticktext
andtickvals
to free-lengthinfo_array
instead of their currentdata_array
val type?
Hmm, possibly... would certainly be nice to keep data_array
out of layout
, but those are potentially long enough (and likely enough to be connected to a data source) to still warrant being called data_array
so I'm a bit ambivalent about it. We could discuss this in a separate issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could discuss this in a separate issue.
Sounds good 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's continue that discussion in -> #1894
|
||
exports.valObjectMeta[opts.valType].coerceFunction(v, propPart, dflt, opts); | ||
|
||
return out; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had change this section to support non-array items
- but also for existing info_array
attributes this should be substantially faster than using regular coerce
for each of the elements, since we're not constructing fake attribute strings and using them in the multiple nestedProperty
constructs that coerce
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing thanks!
src/plots/grid.js
Outdated
editType: 'calc', | ||
description: [ | ||
'Is the first row the top or the bottom? Note that columns', | ||
'are always enumerated from left to right.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
until someone from a RTL country asks I guess? 😅
'*xy* or *x3y2*, or ** to leave that cell empty. You may reuse x axes', | ||
'within the same column, and y axes within the same row.', | ||
'Non-cartesian subplots and traces that support `domain` can place themselves', | ||
'in this grid separately using the `gridcell` attribute.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There would be something nice about specifying non-cartesian subplots in this array as well... but there are two reasons I didn't allow that:
- It's easy to specify
'scene2'
and'geo3'
etc but how do you specify a trace (like pie)? You'd have to useuid
or something. I feel like unless we can get everything, we should only allow cartesian subplots this way. - What if you use the
xaxes
andyaxes
form (or one of the defaults withpattern
) but you still want to insert something else? Like, say you have a splom but you want to put pies along the diagonal, or a splom showing only above the diagonal and you want to put some unrelated subplots below the diagonal?
Also I should mention it might even be useful to allow cartesian axes to be inserted in the grid separately, like with a row
or column
attribute that plucks the appropriate domain
from the grid. Like if you make a splom (using xaxes
and yaxes
) and want something special along the diagonal (box plots for example) - these may want to share a y axis with the grid but have a separate x axis (categorical in that case). We might be able to do this with overlaying
but that might not work because the axis it's overlaying doesn't actually have a subplot on the diagonal... anyway that seems a kind of hacky solution, would be cleaner to just specify the column.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but how do you specify a trace (like pie)?
we could probably do pie
(i.e. first pie trace in gd.data
), pie2
(2nd pie trace in gd.data
), pie3
and similar for other traces that provide their own subplot. But yeah, it's not ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you use the xaxes and yaxes form (or one of the defaults with pattern) but you still want to insert something else? Like, say you have a splom but you want to put pies along the diagonal, or a splom showing only above the diagonal and you want to put some unrelated subplots below the diagonal?
Could we allow setting multiple grids per graph? e.g. grid
, grid2
, grid3
that would overlay one another?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and also allowing multiple grids could be a nice way to generate grids with multi-colspan, multi-rowspan cells and crazy shared axes pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think multiple overlaying grids is likely to be more confusing than helpful. colspan/rowspan would be easier to understand and use as just that - a way to specify colspan and rowspan within the existing grid, some of them wouldn't even work as a different grid, unless you monkey with grid.domain
, which would be super confusing, and getting the gaps to line up correctly would be automatic with colspan/rowspan but nontrivial between grids having a different number of rows and columns.
I suppose for my other use case, inserting extra subplots in an otherwise fully coupled layout, we could support providing xaxes
, yaxes
, and subplots
.
we could probably do
pie
(i.e. first pie trace ingd.data
),pie2
(2nd pie trace ingd.data
),pie3
and similar for other traces that provide their own subplot. But yeah, it's not ideal.
Yeah, my concern with that is order in gd.data
can be changed, or you can delete or change the type of an earlier trace, and then you'd have to know to update the reference in the grid. Also it's ambiguous, does pie2
mean the pie in trace 2 or the second pie?
Anyway, it's pretty low-footprint, if we do think of a nice way to do this I don't think it would be a problem to add it and continue supporting the way I have it now.
src/plots/grid.js
Outdated
'then iterating rows according to `roworder`.' | ||
].join(' ') | ||
}, | ||
gap: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used xgap
and ygap
to match heatmap
, but I felt like it's also useful to have a symmetric gap
attribute (that's only used to set the defaults for xgap
and ygap
) - thoughts? Keep this (and add it to heatmap
), or remove it and just use xgap
/ygap
?
Also, for completeness, in other contexts I've seen either grout
or gutter
used for this effect, do we want to stick with gap
or use one of these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll 🔪 gap
and keep xgap
and ygap
.
The meaning of horizontal/vertical spacing in the python wrapper is different from what I have for xgap
/ygap
- the python version seems to use absolute domain fraction (hence the need to divide the default by the number of cols/rows, whereas in my current implementation it's a fraction of the grid period, ie xgap = xgapPx / (xgapPx + xplotPx)
. That allows me to use a constant value for the default, and I feel like it's easier to use as you can think of how the gap compares to one cell of the grid, rather than how it compares to the whole plot area... do you agree or should I switch to what we did in python?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer your implementation 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔪 gap and made the defaults (for independent axes, not for coupled) match the python version 0.2 and 0.3 instead of the 0.25 for both I had previously. -> 670bdd5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on! I wasn't expecting this much functionality in your first push 💪
I made a few comments. The gap
vs xgap
/ygap
is probably the most blocking one.
"type": "table", | ||
"header": {"values": ["a", "b"]}, | ||
"cells": {"values": [["c", "d"], ["e", "f"]]}, | ||
"domain": {"row": 2, "column": 0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets treat this as a separate issue?
yep that's fine -> https://github.com/plotly/plotly.js/issues/new
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> #2401
otherOpts: ['dflt', 'freeLength'], | ||
// set dimensions=2 for a 2D array | ||
// `items` may be a single object instead of an array, in which case | ||
// `freeLength` must be true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
exports.valObjectMeta[opts.valType].coerceFunction(v, propPart, dflt, opts); | ||
|
||
return out; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing thanks!
.toEqual([[1, 5], [6, 10]]); | ||
}); | ||
|
||
it('supports unbounded 2D freeLength arrays', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding Lib.validate
tests!
Would it too much to ask to add a Plotly.validate
test for the new grid
attribute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, turns out Plotly.validate
didn't dig into info_array
- was there any reason for that? Anyway, now it does -> 40dd784
I had to tweak the behavior there, ''
vs undefined
- seems reasonable, shouldn't affect behavior but cleans up validate
.
src/plots/grid.js
Outdated
'then iterating rows according to `roworder`.' | ||
].join(' ') | ||
}, | ||
gap: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'that each x axis is used in. *top* and *top plot* are similar.' | ||
].join(' ') | ||
}, | ||
yside: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice touch here 👌
@@ -69,4 +71,60 @@ module.exports = function(opts, extra) { | |||
}), | |||
editType: opts.editType | |||
}; | |||
|
|||
if(!opts.noGridCell) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any noGridCell
options being set. 🔪 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noGridCell
shows up in the domain for the grid itself - you can't put the grid in itself! 😄
if(!opts.noGridCell) { | ||
out.row = { | ||
valType: 'integer', | ||
min: 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two suggestions:
- It might be worth adding a
dflt: 0
, so thatdomain: {column: 2}
defaults to subplotxy2
? - It might be nice to allow
domain: {row: ''}
to match the 'x'/'x2'/'x3' counter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth adding a
dflt: 0
, so thatdomain: {column: 2}
defaults to subplotxy2
?
Yeah, makes sense - if you have a grid, by default all subplots should go in it. You can always set explicit domain.x
and domain.y
anyhow, this is only setting the defaults for those attributes -> 4b43e35
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be nice to allow
domain: {row: ''}
to match the 'x'/'x2'/'x3' counter
As discussed offline, I omitted this one (though if you say ''
you'll get 0
now anyway with the change ^^ so the result is the same)
@@ -33,11 +33,6 @@ | |||
"xaxis": { | |||
"type": "category" | |||
}, | |||
"barmode": "group", | |||
"categories": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mock is ancient. Way older than the axis.categories
attribute. This must be a typo. 👍
'*xy* or *x3y2*, or ** to leave that cell empty. You may reuse x axes', | ||
'within the same column, and y axes within the same row.', | ||
'Non-cartesian subplots and traces that support `domain` can place themselves', | ||
'in this grid separately using the `gridcell` attribute.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and also allowing multiple grids could be a nice way to generate grids with multi-colspan, multi-rowspan cells and crazy shared axes pattern.
Apparently I missed parcoords in the Plotly.react cleanup
still don't know why it's failing... but it seems to work when run in a smaller batch
I never did figure out what was causing the failing test, so I just marked it |
} | ||
|
||
return wrap({ | ||
lineColor: color, | ||
cscale: cscale | ||
}); | ||
}; | ||
|
||
function constHalf(len) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making this more readable albeit less functional. 👌
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); | ||
// TODO: I think it would be better to keep showing lines beyond the last line color | ||
// but I'm not sure what color to give these lines - probably black or white | ||
// depending on the background color? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree here.
Currently, scatter traces with marker.color
arrays shorter than the coordinates get black markers - regardless of plot_bgcolor
. Adding some logic around the background color would be even better.
This probably deserves a new issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great -> #2405
assertErrorContent( | ||
out[4], 'dynamic', 'layout', null, | ||
['xaxis', 'range', 1], 'xaxis.range[1]', | ||
'In layout, key xaxis.range[1] (set to \'lots\') got reset to \'50\' during defaults.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Why does xaxis.range[1]
get reset to '50'
? here? I thought the xaxis.range[1]
default was 6?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok thanks 👌
src/plots/domain.js
Outdated
@@ -90,6 +91,7 @@ exports.attributes = function(opts, extra) { | |||
out.column = { | |||
valType: 'integer', | |||
min: 0, | |||
dflt: 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Can we 🔒 this down in a jasmine test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call -> 794669b
That's how many of
I'm happy with parcoords tests that ensure we don't slice arrays during the defaults. |
Great. Looks like all my comments have been addressed. 💃 |
An easy way to make a grid of subplots:
layout.grid
can arrange cartesian subplots either as coupled layouts (x axes span entire columns and y axes span rows) or independent (each grid cell gets a specified x/y subplot; axes are coupled only as specified by the subplots). Other types of subplot (and non-subplot traces that supportdomain
) can be placed in the grid too, by specifyingdomain: {row, column}
Created to support SPLOM #2372 (comment), but hopefully its usefulness will be more general than that.