Skip to content
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

two layer or grouped axes label / ticks #2799

Closed
bpostlethwaite opened this issue Jul 10, 2018 · 22 comments · Fixed by #3300
Closed

two layer or grouped axes label / ticks #2799

bpostlethwaite opened this issue Jul 10, 2018 · 22 comments · Fixed by #3300
Assignees

Comments

@bpostlethwaite
Copy link
Member

A request has come in for a feature that could replicate this multi-level axes ticks / labels.

The following image shows something like an x2 attr on bars.

image

Note that to support positioning axes labels between ticks perhaps this #1673 (comment) will be a requirement.

@nicolaskruchten
Copy link
Member

I propose that we break this up into two batches:

  1. second-level axis labels, no ticks
  2. ticks that extend down from the matching ticks from More control over tick text alignment #1673

@Braintelligence

This comment has been minimized.

@chriddyp
Copy link
Member

This was asked about in the dash community forum today here: https://community.plot.ly/t/boxplot-with-multiple-categories-in-dash/14652/2. interestingly we already have an R example that fakes it: https://plot.ly/r/axes/#subcategory-axes

@larissaleite

This comment has been minimized.

@etpinard
Copy link
Contributor

etpinard commented Nov 2, 2018

This is what I'm thinking:

(x|y)axis.categorygroups: [{
  name: 'Group 1',
  values: ['a', 'b', 'c'],
  level: 1 || 2 // (to start, more level later),
  bgcolor: '',
  side: '',
  font: {},
  // more style/positioning things ...
}, {
  // ...
},
// ...
]

where categoryarray would not get coerced when categorygroups is set, the ...categorygroups.map(c => c.values) would be the category list.

@nicolaskruchten
Copy link
Member

Hmmm to me this is more of a data level consideration than a layout.(x|y)axis one...

data = [{ 
x: ["q1", "q2", "q3", "q4","q1", "q2", "q3", "q4"], 
x2: ["2017","2017","2017","2017","2018","2018","2018","2018",] 
...}]

@alexcjohnson
Copy link
Collaborator

It probably has to be both - the data trace needs to have multiple x arrays, and the axis needs style, order, and optional values for each level.

The categories in an inner level need not be the same for each instance of an outer level. There could be missing values (q4 2018 hasn't happened yet, for example) and we should honor that, or the inner level values could be be totally different from one outer value to the next - eg https://peltiertech.com/chart-with-a-dual-category-axis/
subcategories

An axis.categorygroups container array seems reasonable. Re ^^ seems like values (if not generated automatically from the data) would need to include duplicates, unless we make an attribute (outside categorygroups I guess) to explicitly require/create a cartesian product. So for the example plot up top, we'd have:

categorygroups: [{
  name: 'Year',
  values: [16, 17, 18, 16, 17, 18, 16, 17, 18, 16, 17, 18, 16, 17, 18, 16, 17, 18],
  order: 'category ascending'
}, {
  name: 'Rating',
  values: ['BB+', 'BB+', 'BB+', 'BB', 'BB', 'BB', 'BB-', 'BB-', 'BB-', 'B+', 'B+', 'B+', 'B', 'B', 'B', 'B-', 'B-', 'B-'],
  order: 'array'
}]

order (which would be most useful when taking values from the data) would then sort all these values arrays together, sorting first on the last array (and requiring matching values in the last array to be grouped together regardless of anything else) and working back up to the first array. (Or do we want to reverse the order here, put the outer category first, the inner last? That actually may make more sense...)

Is level necessary? Isn't it just the index in categorygroups?
What would side do per group? Isn't axis.side enough?
name I guess is for hover labels?
What's bgcolor for?
One set of things that will definitely go in there is tick & grid attributes - people will want to for example draw solid gridlines for the outer categories but dashed or no gridlines for inner categories.

@nicolaskruchten
Copy link
Member

It probably has to be both - the data trace needs to have multiple x arrays, and the axis needs style, order, and optional values for each level.

Yes, but the base case is that it'll just be specified in data I would expect, certainly from the Editor POV. I would be fine with supporting just the data piece first and then layering on the customizability in layout in a second pass.

The categories in an inner level need not be the same for each instance of an outer level.

Yes, of course, there's no coupling there.

@alexcjohnson
Copy link
Collaborator

Yes, but the base case is that it'll just be specified in data I would expect

That might be sufficient... we just look for x2 (and eventually x<n>) in the data traces, and if that exists we "autotype" the axis to 2-level categorical?

@nicolaskruchten
Copy link
Member

if that exists we "autotype" the axis to 2-level categorical?

That was what I was thinking yes. Any downside to this?

@nicolaskruchten
Copy link
Member

Also, ideally the order would default to the data order such that in cases like {x: [1,2,3], x2: [4,5,4]} the result would be that x would end up in [1,3,2] order so as not to break up the 4 in x2... that makes sense right?

@nicolaskruchten
Copy link
Member

It occurs to me that from an RCE perspective at least, treating x as a 2-d array (like z in heatmap) is muuuch easier than having x, x2, x3 etc... Thoughts?

Also: I could easily see this feature being scoped tightly to bar and heatmap-like traces only, if that makes implementation easier.

@etpinard
Copy link
Contributor

etpinard commented Nov 7, 2018

In @nicolaskruchten 's

data = [{ 
x: ["q1", "q2", "q3", "q4","q1", "q2", "q3", "q4"], 
x2: ["2017","2017","2017","2017","2018","2018","2018","2018",] 
...}]

the x categories here will have to behave differently than for "traditional" type: 'category' axes. The "effective" categories are x[i] + y[i] - which we'll map 1-to-1 to integers >=0. So maybe adding axis.type: 'category2' or groupedcategory or categorygroup would make sense.

By the way, I'm not a big fan of x2 - it sounds potentially confusing with trace attributes xaxis: 'x2'. Maybe xgroup would be best.


Let me try to make my first categorygroups specs attempt clearer. To describe the graph in the PR description, we'd get:

xaxis.categorygroups: [{
  name: 'BB+',
  values: ['16', '17', '18']
}, {
  name: 'BB',
  values: ['16', '17', '18']
}
// ...
]

and with level we could add an additional label level (to be drawn below the BB-, BB, ...) e.g.

xaxis.categories: [
// ...
{
  name: 'Double letters',
  values: ['BB', 'BB-', 'BB+'],
  level: 2
}, {
  name: 'Single letters',
  values: ['B', 'B-', 'B+'],
  level: 2
}]

.... but maybe keeping things "flat" would be better 🤔


What would side do per group? Isn't axis.side enough?
What's bgcolor for?

To replicate things like:

image

@nicolaskruchten
Copy link
Member

nicolaskruchten commented Nov 8, 2018 via email

@etpinard
Copy link
Contributor

Quick question: how should we handle overlapping labels?

Consider something like:

image

Here the loooooooooong label if drawn horizontally overlaps the q3 label. Rotating all labels by 30 degrees removes the overlap. Now, should we draw the secondary labels (here 2017 and 2018) below the loooooooooong bounding box?

To me, that would make multi-category axes look a little odd. I'm thinking perhaps we should truncate the "upper" labels instead of rotating them on multi-category axes. Does anyone have a strong opinion against truncating long tick labels scenarios like this one?

@alexcjohnson
Copy link
Collaborator

I don't think 30-degree rotation makes sense for multi-category axes, for the upper OR lower labels. But 90-degree rotation determined separately for each level) is both reasonable and common.
multilevel

I don't think truncation should be used at all by default, but it could be a nice option to expose.

@nicolaskruchten
Copy link
Member

Wow that's a hard to grok chart! Compare 2017 across categories :)

I generally agree with:

I don't think 30-degree rotation makes sense for multi-category axes, for the upper OR lower labels. But 90-degree rotation determined separately for each level) is both reasonable and common.

@alexcjohnson
Copy link
Collaborator

Wow that's a hard to grok chart! Compare 2017 across categories :)

Indeed, I've been pondering if there's a way to improve that. Certainly showing gridlines for the outer category only would help. Trying to think through an analog of bargap / bargroupgap, ie to give a bigger gap at the outer category boundary... but it seems tough to square that with the ticks & gridlines. Also would probably be a major headache to implement, as the spacing between categories would no longer be constant.

@nicolaskruchten
Copy link
Member

nicolaskruchten commented Nov 27, 2018 via email

@markjamie

This comment has been minimized.

@etpinard
Copy link
Contributor

Oh, one more question I forget to ask this morning:

Should we add support for type: 'multicategory' on y-axes in the first iteration?

@etpinard
Copy link
Contributor

Should we add support for type: 'multicategory' on y-axes in the first iteration?

From in-person @nicolaskruchten : yeah, let's do that in the first iteration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants