Search code examples
pythonplotlyplotly-dash

How to align text left on a plotly bar chart (example image contained) [Plotly-Dash]


I need help in adding text to my graph.

I have tried text = 'y' and text-position = 'inside' but the text goes vertical or gets squashed for small bar charts so it can fit inside the bar. I just want it to write across.

Here is a working example of the code that needs fixing:

app = dash.Dash(__name__)
app.css.append_css({'external_url': 'https://codepen.io/amyoshino/pen/jzXypZ.css'})

    labels1 = ['0-7', '8-12', '13-15', '16-20', '21-25', '26+']
values1 = [10, 30, 10, 5, 6, 8]


labels2 = ['India', 'Scotland', 'Germany', 'NW England', 'N Ireland', 'Norway', 'NE England', 'Paris', 'North Africa', 'scandinavia']
values2 = [1, 0, 4, 9, 11, 18, 50, 7, 0, 2]

values3 = [10, 111, 75, 20]
labels4 = ['Safety Manager', 'Office Administrator', 'Internal Officer', 'Assistant Producer']

bar_color = ['#f6fbfc', '#eef7fa', '#e6f3f7', '#deeff5', '#d6ebf2', '#cde7f0', '#c5e3ed', '#bddfeb', '#b5dbe8', '#add8e6']
bar_color2 = ['#e6f3f7', '#deeff5', '#d6ebf2', '#cde7f0', '#c5e3ed', '#bddfeb', '#b5dbe8', '#add8e6']

app.layout = html.Div([
  html.Div([ 
    html.Div([
        dcc.Graph(id = 'age',
                          figure = {
                                    'data': [go.Bar(x = values1,
                                                    y = labels1,
                                                    orientation = 'h',
                                                    marker=dict(color = bar_color2),
                                                    text = labels1,
                                                    textposition = 'inside'
                                                    )
                                            ],
                                    'layout': go.Layout(title = 'Number of respondees per tenure',
                                                        yaxis=dict(
                                                                   zeroline=False,
                                                                   showline=False,
                                                                   showgrid = False,
                                                                   autorange="reversed",
                                                                   ),
                                                            xaxis=dict(
                                                                      zeroline=False,
                                                                      showline=False,
                                                                      showgrid = False
                                                                      )
                                                       )
                                  }
                         )
    ], className = 'four columns'),


    html.Div([
       dcc.Graph(id = 'location',
                                 figure = {
                                          'data': [go.Bar(x = values2,
                                                          y = labels2,
                                                          orientation = 'h',
                                                          marker=dict(color = bar_color),
                                                            text = labels2,
                                                            textposition = 'inside'
                                                         )
                                                  ],
                                          'layout': go.Layout(title = 'Number of respondees per region',
                                                                yaxis=dict(
                                                                          zeroline=False,
                                                                          showline=False,
                                                                          showgrid = False,
                                                                          autorange="reversed",
                                                                         ),
                                                                xaxis=dict(
                                                                          zeroline=False,
                                                                          showline=False,
                                                                          showgrid = False
                                                                         )                                                             ) 
                                        }
                                )
        ], className = 'four columns'),

    html.Div([
            dcc.Graph(id = 'job',
                                  figure = {
                                            'data': [go.Bar(x = values3,
                                                            y = labels4,
                                                            orientation = 'h',
                                                            marker=dict(color = bar_color2),
                                                            text = labels4,
                                                            textposition = 'inside'                                                            
                                                           )
                                                    ],
                                           'layout': go.Layout(title = 'Number of respondees per role',
                                                               yaxis=dict(
#                                                                         automargin=True,
                                                                          zeroline=False,
                                                                          showline=False,
                                                                          showgrid = False,
                                                                          autorange="reversed",
                                                                         ),
                                                                xaxis=dict(
                                                                          zeroline=False,
                                                                          showline=False,
                                                                          showgrid = False
                                                                         )
                                                              ) 
                                           }
                                )
        ], className = 'four columns')


  ], className = 'row')

])

if __name__ == '__main__':
app.run_server()

Here's the output:

enter image description here

Here's an example of how I want my text to look: enter image description here

I need help with two things:

  1. Make the text align to the left not the right of the bar.
  2. If the bar length is short I want the text to still be visible (even if the bar length is zero) and not squashed or vertically aligned.

If you can also give an explanation of how to fix y-axis being cut off in the third chart that would be amazing. For now, I have to change the labels to force it to fit which is time-consuming. Is there a way of adding padding to the container or something?

Thanks.


Solution

  • This is an inelegant workaround, but after scouring the plotly python docs, I couldn't find anything that would do exactly what you were asking with the plotly attributes provided. If you need a one-time, quick fix now, try using yaxis=dict(showticklabels=False) and add your labels manually as annotations like:

    layout = go.Layout(
        # Hide the y tick labels
            yaxis=dict(
            showticklabels=False),
        annotations=[
            dict(
            # I had to try different x values to get alignment
                x=0.8,
                y='giraffes',
                xref='x',
                yref='y',
                text='Giraffes',
                font=dict(
                    family='Arial',
                    size=24,
                    color='rgba(255, 255, 255)'
                ),
                align='left',
            # Don't show any arrow
                showarrow=False,
            ), 
    

    The output I got looked like: Plotly Horizontal Bar with Annotations for Labels

    You can check the Plotly Annotations and Chart Attributes documentation to see if there is anything that better suits your needs.

    Edit: I started posting this response before the code was added to the question. Here is an example of how the annotations could be made for the first two y labels of the first graph in the code in question:

    app.layout = html.Div([
      html.Div([ 
        html.Div([
            dcc.Graph(id = 'age',
                              figure = {
                                        'data': [go.Bar(x = values1,
                                                        y = labels1,
                                                        orientation = 'h',
                                                        marker=dict(color = bar_color2),
                                                        text = labels1,
                                                        textposition = 'inside'
                                                        )
                                                ],
                                        'layout': go.Layout(title = 'Number of respondees per tenure',
                                                            yaxis=dict(
                                                                       zeroline=False,
                                                                       showline=False,
                                                                       showgrid = False,
                                                                       showticklabels=False
                                                                       autorange="reversed",
                                                                       ),
                                                                xaxis=dict(
                                                                          zeroline=False,
                                                                          showline=False,
                                                                          showgrid = False
                                                                          )
                                                                       ),
                                                                annotations=[dict(
                                                                            x=0.8,
                                                                            y=labels1[0],
                                                                            xref='x',
                                                                            yref='y',
                                                                            text=labels1[0],
                                                                            font=dict(
                                                                                family='Arial',
                                                                                size=24,
                                                                                color='rgba(255, 255, 255)'
                                                                            ),
                                                                            align='left',
                                                                            showarrow=False,
                                                                        ), 
                                                                        dict(
                                                                            x=1.2,
                                                                            y=labels1[1],
                                                                            xref='x',
                                                                            yref='y',
                                                                            text=labels1[1],
                                                                            font=dict(
                                                                                family='Arial',
                                                                                size=24,
                                                                                color='rgba(255, 255, 255)'
                                                                            ),
                                                                            align='left',
                                                                            showarrow=False,
                                                                        ),
    

    Edit 2: @ user8322222, to answer the question in your comment, you could use a list comprehension to make your annotations dictionary like so:

     annotations1 = [dict(x=(len(labels1[i])*0.15), y=labels1[i], xref='x', yref='y', 
    text=labels1[i], font=dict(family='Arial', size=24, color='rgba(255, 255, 255)'),
          align='left', showarrow=False) for i in range(len(labels1))]
    

    However I don't think there will be a constant you could multiply by the length of the text in characters (like I used for x in the example) to get perfect alignment. You could use the pixel length or other measures for the string as in this post to devise a more accurate way of determining x to get it properly aligned. Hope that helps.