plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.72k stars 1.83k forks source link

Subplot titles #2746

Open danydjedocr opened 6 years ago

danydjedocr commented 6 years ago

I have 3 subplots that are synchronized by sharing their XAxis. I want to add a title for each of the subplots like this :

plotly_issue

See this pen : https://codepen.io/anon/pen/mKpNvO

etpinard commented 6 years ago

Hmm. I think someone already opened an issue about this, but I can't find it.

Related to https://github.com/plotly/plotly.js/issues/2274 and https://github.com/plotly/plotly.js/issues/977

etpinard commented 6 years ago

As a workaround, this can be done using layout annotations:

https://plot.ly/javascript/text-and-annotations/#paper-referenced-annotations

etpinard commented 6 years ago

also related to https://github.com/plotly/plotly.js/issues/233

timini commented 5 years ago

Annotations not working for subtitle

alexsax commented 5 years ago

@etpinard Is there a way to ensure the titles are centered above their subplots, using layout annotations? I can't find a good way to keep the titles centered for screens of different sizes. It would also be great to do this in a responsive way.

Subplots with titles seems like a common use case, so I'm sure that this feature must be implemented somewhere!

JivanRoquet commented 5 years ago

There are no words to describe how awkward the "easy" paper-referenced annotations solution is for achieving the simple and common task which is giving titles to subplots.

Especially when you want each title to be precisely centered along its own subplot (apparently there is no way do to this without manually calculating the global x value for each of them, taking the gap between subplots into account, etc).

Basically, subplots allow to get rid of complex and cumbersome calculations to get the positioning of the x/y axis elements right. If you dare want to add titles to your subplots (which is the case for, I don't know, let's say 80% of the time?), then these calculations are back again just for the sake of the titles, and in a much weirder way even (because of the borders, gaps, etc, that have to be take in to account).

As @alexsax puts it, subplots with titles seem like a common use case, so maybe this would be a good candidate to simply "give suplots a title" before implementing some weird complicated edge case stuff that 0.0001% of users will ever need, like, pointing an annotated arrow to a bubble in a 3d-spheres chart so that the arrow dynamically follows the rotation of the whole chart...

JivanRoquet commented 5 years ago

Update: it looks like there really is no way to center the title on its own subplot, as @alexsas puts it.

Providing a xref: 'subplot' option would solve this issue, like the following:

annotations: [
  {
    xref: 'subplot',
    subplot: [0],
    x: 0.5,
    xanchor: 'middle',
    text: 'I am centered on the first subplot'
  },
  {
    xref: 'subplot',
    subplot: [1],
    x: 0.5,
    xanchor: 'middle',
    text: 'I am centered on the second subplot'
  },
]
mjh7 commented 4 years ago

I agree this kind of thing is lacking in subplots - I'm new to plotly and after I saw how powerful it is I created a many-part subplot assuming subplot titling and legends would be easy, but when I got to that I went on a bit of a goose chase trying to figure it out.

For now, the solution that is working for me (also based on annotations) makes use of the "domain"-splitting that happens when calling make_subplots. In python, I'm populating my subplots in a loop (starting at i=1) and using 'paper' anchors. My subplots only have one column, but you could do the same thing for getting the bottom position of a subplot using .domain[0] or the left/right using the corresponding xaxis attributes. Here are the most relevant lines within the loop:

max_pos = fig['layout']['yaxis' + str(i)].domain[1]
annotations.append({'text': idx, 'xref': 'paper', 'yref': 'paper', 'x': .5, 'y': max_pos, 'xanchor': 'center', 'yanchor': 'bottom', 'showarrow': False, 'font': dict(size = 22)}

...and then after end of loop:

fig['layout'].update(height = 5000, width = 1000, title='Your Title Here', annotations=annotations)
kierenj commented 4 years ago

This would be super incredibly massively turbo extra useful for our project

Edit: @etpinard if you could explain what you mean by easy please - how do I make a paper-referenced annotation relative to subplot #3, for instance? I have a vertically-stacked list of "N" subplots, different Y axis for each. thank you

My issue: if I use paper, it's not relative to the axis I want. If I use the axis, the axis is extended to cover the annotation: I don't want that, I want to have text above the axis

This is what I've done elsewhere: it's so hacky!

annotations: plots.map((p, i) => ({
              text: p.title,
              showarrow: false,
              xref: "paper",
              yref: "paper",
              xanchor: "center",
              yanchor: "center",
              x: 0.5,
              y: (1 - i / plots.length) * 1.1 - 0.06
            }))

For this one I had to avoid the final shared x axis too:

{
                    text: text,
                    font: {
                      color: "#707070"
                    },
                    showarrow: false,
                    xref: "paper",
                    yref: "paper",
                    xanchor: "right",
                    yanchor: "top",
                    x: 1,
                    y:
                      (1 - (pi + 1) / plots.length) * 1.04 -
                      (pi == plots.length - 1 ? 0.02 : 0)
                  }
umesh-timalsina commented 4 years ago

I think there is a decent use case for most users to incorporate subplot titles, positioning etc... This will be a hassle if your plan to do something dynamic with plotly.js. Basically, you will be left to do all your calculations. For example, how many rows you want to have/ how many columns, how would you space your sub plots. Is the spacing going to be enough? The easy solution would be to create multiple Plotly.js Plots one for each subplot. This should be supported by plotly.js IMO.

jackparmer commented 3 years ago

This issue has been tagged with NEEDS SPON$OR

A community PR for this feature would certainly be welcome, but our experience is deeper features like this are difficult to complete without the Plotly maintainers leading the effort.

Sponsorship range: $15k-$20k

What Sponsorship includes:

Please include the link to this issue when contacting us to discuss.

tomdewar commented 3 years ago

This should be out-of-the-box functionality. By now, users worldwide must have wasted thousands of hours trying to achieve what should be the very basic job of putting titles on subplots. Yours in continued frustration

nicolaskruchten commented 3 years ago

Since we added the ability to do xref: "x domain" last summer, for 2d cartesian axes it is now possible to position annotations with respect to subplots without doing bookkeeping around the sublot domains, as per this pen: https://codepen.io/nicolaskruchten/pen/mdROeJv ... try it by messing with the domains of the axes and you'll see that the annotations stick where you'd expect.

Here's the output and code:

newplot

  {
    data: [{
      mode: "markers",
      x:[1,3,3],
      y:[2,1,3],
      xaxis: "x",
      yaxis: "y"
    },{
      mode: "markers",
      x:[1,3,3],
      y:[2,1,3],
      xaxis: "x2",
      yaxis: "y2"
    }],
    layout: {
      xaxis: { 
        domain: [0,0.48] ,
        title: "X1"
      },
      yaxis: { 
        domain: [0,0.48],
        title: "Y1"
      },
      xaxis2: {
        domain: [0.52, 1],
        anchor: "y2",
        title: "X2"
      },
      yaxis2: {
        domain: [0.52, 1],
        anchor: "x2",
        title: "Y2"
      },
      annotations: [
        {
          text: "X1/Y1 title",
          showarrow: false,
          x: 0,
          xref: "x domain",
          y: 1.1,
          yref: "y domain"
        },
        {
          text: "X2/Y2 title",
          showarrow: false,
          x: 0,
          xref: "x2 domain",
          y: 1.1,
          yref: "y2 domain"
        }
      ]
    }
  }
nicolaskruchten commented 3 years ago

Note that this issue will remain open, because the above is not a general-purpose solution for all subplot types e.g. polar, scene, ternary, mapbox, geo etc.

pkofod commented 3 years ago

I suspect that your solution might be missing the point. Unless I am missing the point :) I'm a very happy user of PlotlyJS.jl, but one annoying thing is that it uses domain for subplots and not grids . Is it not correctly understood that you can't generally place subplotittles above grid-based-subplots?

nicolaskruchten commented 3 years ago

My solution addresses the examples given above :) Did you mean grids i.e. using the layout.grid parameter?

nicolaskruchten commented 3 years ago

Here's the same annotation solution with xref="x domain" on axes implicitly positioned with layout.grid: https://codepen.io/nicolaskruchten/pen/ExZNPbz

Note that in general, Plotly.js does not have a first-class concept of 2d cartesian "subplots". There are x-axes and y-axes and traces. If a trace exists at the intersection of a given x and y axis, then a subplot is implicitly drawn there (in my example here and the one above, there are 2 x-axes and 2 y-axes and 2 traces, and the way the trace-to-axis mapping is set up, we end up with one x1y1 subplot and one x2y2 subplot). The layout.grid parameter basically enables one to avoid having to manually position these axes.

(Edit: for completeness... there could be traces that sit at the intersection of x1 and y2 or x2 and y1, in which case extra plotting areas would appear at the domain intersections of those axes)

pkofod commented 3 years ago

Okay. Yes I did mean layout.grid. I'm aware that they plot at intersections - and I like that functionality - the problem is just that if you use domain then you can often get axislabels overlapping other plots. The answer is of course then that the domains have endpoints too near each other, but this calculation is not easy to do if you don't want to have too much space in the situation where the axislabels won't overlap. I'm not sure if that's a clear description or not.

Maybe I should ask (and maybe this is the wrong place): do you have a devdoc or something that explains how different widths, heights and placements are calculated?

nicolaskruchten commented 3 years ago

Got it. So above and beyond the fact that Plotly.js doesn't have a first-class "subplot" concept, Plotly.js also doesn't really do any kind of automated layout beyond automatically growing the plot margins to leave enough room for legends (by default) and 2d cartesian tick labels (opt-in via layout.*axis.automargin).

To address the kinds of concerns raise in this issue beyond the annotations-based approach I've outlined, we would need to do a lot of work within the engine (for which we would need a sponsor!):

  1. we would need to create this first-class "subplot" entity to which we could add titles as strongly-linked entities (i.e. given a subplot, be able to consistently retrieve its title, which is not possible with the implicit annotations approach outlined above)
  2. we would need to engineer an automated layout system beyond that in layout.grid which would lay out subplots with respect to each other, leaving enough room between them for their titles, based on computing the bounding boxes of the titles (which can span multiple lines, include LaTeX, be of variable height based on font, boldness of text etc)

To answer your last question, yes, some of this is documented in the Plotly.py documentation here https://plotly.com/python/figure-structure/ under "Positioning With Paper, Container Coordinates, or Axis Domain Coordinates"

datafj commented 2 years ago

Currently my solution to this problem is adding subplot titles as text annotation within each own subplot, not in the overall plot.

Set yref="paper" and yanchor = "bottom" and y = 1, so that the subplot title will be above each subplot.

And set xref = "paper" and xanchor = "center" and x = 0.5, if you want to center the subplot title within each subplot. Or xanchor = "left" and x = 0, if you want to left align the subplot title.

To get extra space for first row subplot titles, I add extra margin on the top of the overall plot.

To get extra space for second row subplot titles, I add extra margin among the subplots.