Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suppress y axis label in plotly subplot, annotation misalignment

Tags:

python

plotly

I have 2 heatmaps I'm trying to join together, one contains week over week data and the other contains 6W/YTD information. I keep them separate so their colors aren't skewed.

When they're put together in a subplot, the yaxis label on the right is the first row label on the left

enter image description here

I would like to remove that yaxis label, and have tried by means of fig.update_yaxes(title=''/None/False), also the title_text argument, I've also tried accessing it via fig['layout']['scenes']['yaxis2']['title'] = ''/None/False. Seems like a lot of resources (including official documentation) show how to change a single figure, as could be demonstrated with my code via

fig1['layout']['yaxis']['title']='This works with a single plot'
fig1.show()

which unfortunately doesn't carry over when fig2 is added to the subplot. I'm not sure how this assignment is happening. I've reviewed the JSON structure and see no assignment, I've also dug through the structure in their documentation to see if there was something I could override or set. Can someone help me figure out how to hide the fig2 yaxis label? Might be more accurate to ask how one would set this manually, but nonetheless.


*edit* I've taken a closer look at the annotations. The figures annotations are set with this bit

annotations=wow_annot+totals_annot

these are based on

wow['data_labels'] = int_to_str(wow['data'])
totals['data_labels'] = int_to_str(totals['data'])

which are just 2d arrays of integers. checking the annotations in each figure for 'A', the Figure they're added to as subplots is the only one that contains the 'A'

(Pdb) [i for i in fig1.layout.annotations if i.text == 'A']
[]
(Pdb) [i for i in fig2.layout.annotations if i.text == 'A']
[]
(Pdb) [i for i in fig.layout.annotations if i.text == 'A']
[layout.Annotation({
    'font': {'size': 16},
    'showarrow': False,
    'text': 'A',
    'textangle': 90,
    'x': 0.98,
    'xanchor': 'left',
    'xref': 'paper',
    'y': 0.5,
    'yanchor': 'middle',
    'yref': 'paper'
})]

based on that, this could easily be "solved" retroactively by overwriting the annotation like so

new_annot = []
for i in fig.layout.annotations:
  if i.text == wow['y_labels']['labels'][0]:
    i.text = ''
  new_annot.append(i)

fig.update_layout(annotations=new_annot)

this works, but feels very finicky and I would still like to know how this is supposed to be done with plotly. This approach feels like it could have unintended effects depending on application.


Upon closer inspection I'm also realizing there are no annotations placed on the first 2 columns of the bottom row despite them having annotations in their original figure

(Pdb) fig1.layout.annotations[:2]
(layout.Annotation({
    'font': {'color': 'black'}, 'showarrow': False, 'text': '0', 'x': 'W21', 'xref': 'x', 'y': 'A', 'yref': 'y'
}), layout.Annotation({
    'font': {'color': 'black'}, 'showarrow': False, 'text': '0', 'x': 'W22', 'xref': 'x', 'y': 'A', 'yref': 'y'
}))

I'm not sure if there's something I'm missing or if it's my approach that's incorrect in setting annotations

Checking `wow_annot+totals_annot` for `W21:A` annotation
layout.Annotation({
    'font': {'color': 'black'}, 'showarrow': False, 'text': '0', 'x': 'W21', 'xref': 'x', 'y': 'A', 'yref': 'y'
})
Checking the final `fig` for `W21:A` annotation
> d:\test_subplots.py(178)<module>()
-> fig.show()
(Pdb) len([i for i in totals_annot if i.y == 'A'])
2
(Pdb) len([i for i in wow_annot if i.y == 'A'])
6
(Pdb) len([i for i in totals_annot+wow_annot if i.y == 'A'])
8
(Pdb) len([i for i in fig.layout.annotations if i.y == 'A'])
6

I'll leave it as is because this post is becoming cumbersome, but there's a problem: 1) with the annotations and 2) the y title for fig2; I feel like they have to be related though I don't know how this is occurring


I've separated my code below, a Paste is available here.

imports

# Success Criteria for this exercise is a subplot containing 2 Heatmaps side by side in the same Figure
from pdb import set_trace
from covidDash.plot_handlers.colorscales import bone_r # this is a custom derived from matplotlib
from plotly.subplots import make_subplots
import plotly.figure_factory as ff
import plotly.graph_objects as go

data prep

# DATA PREP SECTION
# wow Heatmap data
wow = {'x_labels' : {'name' : 'Week',
                     'labels' : ['W21', 'W22', 'W23', 'W24', 'W25', 'W26']
                    },
       'y_labels' : {'name' : 'Site',
                     'labels' : ['A', 'B', 'C', 'D', 'E', 'F', 'G']
                    },
       'data'     : [
                      [0, 0, 1, 0, 0, 0],
                      [0, 0, 3, 1, 0, 0],
                      [0, 0, 0, 0, 0, 0],
                      [0, 0, 0, 0, 0, 1],
                      [0, 0, 0, 0, 0, 0],
                      [0, 0, 1, 0, 0, 0],
                      [0, 0, 0, 0, 0, 0]
                    ],
       'data_labels' : []
      }


# 6w and YTD Heatmap data
totals = {'x_labels' : {'name' : 'Week',
                        'labels' :['6W', 'YTD' ]
         },
          'y_labels' : wow['y_labels'],
          'data'     : [
                         [1, 16],
                         [4, 8],
                         [0, 1],
                         [1, 12],
                         [0, 5],
                         [1, 17],
                         [0, 1]
                      ],
         'data_labels' : []
         }


# this function is simply a base func for now
def int_to_str(arr2d):
  """base function for handling data to label conversion
  Args:

    arr2d (list): a 2D array with numeric values

  Returns:

    r_data (list): a 2D array with values converted into strings

  """
  r_data = []
  for row in arr2d:
    new_row = []
    [new_row.append(str(n)) for n in row]
    r_data.append(new_row)
  return r_data


wow['data_labels'] = int_to_str(wow['data'])
totals['data_labels'] = int_to_str(totals['data'])

plot_prep

# PLOT PREP SECTION 
# colorbar placement
wow_cbar= {
      'x' : 1.0,
      'title' : {
        'text' : 'Wow',
        'side' : 'right'
      }
    }


total_cbar = {
        'x' : 1.05,
        'title' : {
          'text' : 'Totals',
          'side' : 'right'
         }
       }

# xaxis conf
xaxis_conf={'rangeslider': {'visible': True},
       'type' : 'category',
       'side' : 'top'
      }

creating and joining the figures

# week over week figure
fig1 = ff.create_annotated_heatmap(x=wow['x_labels']['labels'],
                                  y=wow['y_labels']['labels'],
                                  z=wow['data'], 
                                  colorscale=bone_r,
                                  font_colors=['black','white'],
                                  showscale=True,
                                  annotation_text=wow['data_labels'],
                                  colorbar=wow_cbar,
                                  name='Wow'
                                  )


# 6W and YTD
fig2 =ff.create_annotated_heatmap(x=totals['x_labels']['labels'],
                                  y=totals['y_labels']['labels'],
                                  z=totals['data'], 
                                  colorscale=bone_r,
                                  font_colors=['black','white'],
                                  showscale=True,
                                  annotation_text=totals['data_labels'],
                                  colorbar=total_cbar,
                                  name='Totals',
                                  )
# SUBPLOT PREP SECTION
# base subplot
fig = make_subplots(
    rows=1, cols=2,
    shared_yaxes=True,
    horizontal_spacing=0,
    row_titles=wow['y_labels']['labels'],
#    y_title=[wow['y_labels']['name'],
    x_title=wow['x_labels']['name'],
    column_widths=[0.75, 0.25]
)

# add data 
fig.add_trace(fig1.data[0], 1, 1)
fig.add_trace(fig2.data[0], 1, 2)

# apply annotations
wow_annot = list(fig1.layout.annotations)
totals_annot = list(fig2.layout.annotations)
for k in range(len(totals_annot)):
  totals_annot[k]['xref'] = 'x2'
  totals_annot[k]['yref'] = 'y2'

fig.update_layout(annotations=wow_annot+totals_annot,xaxis=xaxis_conf, xaxis2={'side':'top'})
set_trace()

fig.show()
like image 322
saniboy Avatar asked Jun 30 '20 20:06

saniboy


1 Answers

something's wrong with fig.update_layout(annotations=foo), I was able to resolve this by applying the annotations directly by re-arranging as so

fig.update_layout(xaxis=xaxis_conf, xaxis2={'side':'top'})
fig['layout']['annotations'] = wow_annot+totals_annot

this makes it so all of the fields are properly annotated and a y_label is no longer assigned.

as to what the specific problem is with .update_layout, I'm not sure but this is how one would go about resolving this issue if yaxis_title=foo didn't work for you.

like image 120
saniboy Avatar answered Oct 06 '22 15:10

saniboy