Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 808
user8322222 Avatar asked Mar 28 '19 11:03

user8322222


3 Answers

You can pass text into go.Bar(), where you can set textposition="inside" and insidetextanchor="start", which should solve this issue.

enter image description here

fig = go.Figure(go.Bar(
            x=[20, 14, 23],
            y=['giraffes', 'orangutans', 'monkeys'],
            orientation='h',
            # define the annotations
            text=['giraffes', 'orangutans', 'monkeys'],
            # position, "auto", "inside" or "outside"
            textposition="auto",
            # anchor could be "start" or "end"
            insidetextanchor="start",
            insidetextfont=dict(family='Times', size=13, color='white'),
            outsidetextfont=dict(family='Times', size=13, color='white')))
fig.update_layout(
    yaxis=dict(
        showticklabels=False,
    ))
fig.show()
like image 159
Richard Xue Avatar answered Nov 17 '22 10:11

Richard Xue


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.

like image 29
tjeffkessler Avatar answered Nov 17 '22 11:11

tjeffkessler


You can prevent the y-axis from being cutoff in your third chart by changing the margins of the figure. Add the following code to the inside of the call to go.Layout():

margin=go.layout.Margin(
        l=150, # left margin, in px
        r=80, # right margin, in px
        t=80, # top margin, in px
        b=80, # bottom margin, in px
        pad=0
        )

You can adjust the left margin for different y-axis labels, or you could set it to automatically scale with the length of the longest label.

Third plot without y-axis cutoff

like image 1
Nathaniel Avatar answered Nov 17 '22 12:11

Nathaniel