Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotly graph component cannot accept viewport units to set text annotation font size

I am using Plotly-Dash and need the font size of my text annotations to scale with the viewport width, as my graphs do. For various headers in my layout, I am able to directly set font-size: '1vw', however vw is not an accepted unit for setting the font-size for the style attribute of a dcc.Graph component. Here is the associated traceback:

ValueError: Invalid element(s) received for the 'size' property of scatter.textfont Invalid elements include: ['1vw', '1vw', '1vw', '1vw', '1vw', '1vw', '1vw', '1vw', '1vw', '1vw']

The 'size' property is a number and may be specified as:
  - An int or float in the interval [1, inf]
  - A tuple, list, or one-dimensional numpy array of the above

I figure that if the dcc.Graph component can accept viewport units (e.g. style = {height: 30vw, width: 30vw}) and simply convert them to pixels browser-side, then I should be able to perform a similar conversion with the font-size.

Is there a means on the Python side to retrieve the viewport width in pixels, so that I can perform my own scaling logic for the font size?

Here is a sample Dash application that demonstrates this behavior:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go

labels = {'Point 1': (3.5,5), 'Point 2': (1.5,2), 'Point 3': (3.5,8)}

app = dash.Dash(__name__)

app.layout = html.Div([

    html.H1('Test', id='test', style={'margin': 50}),

    dcc.Graph(id='my-plot', style={'margin': 10}),

])

@app.callback(
    Output('my-plot', 'figure'),
    [Input('test', 'children')])
def update_graph(val):

    return {

        'data': [go.Scatter(
            x=[v[0]],
            y=[v[1]],
            text=k,
            mode='text'
        ) for k, v in labels.items()],

        'layout': go.Layout(
            margin={'l': 40, 'b': 40, 't': 40, 'r': 40},
            shapes=[
                {
                    'type': 'path',
                    'path': ' M 1 1 L 1 3 L 4 1 Z',
                    'fillcolor': 'rgba(44, 160, 101, 0.5)',
                    'line': {
                        'color': 'rgb(44, 160, 101)',
                    }
                },
                {
                    'type': 'path',
                    'path': ' M 3,7 L2,8 L2,9 L3,10, L4,10 L5,9 L5,8 L4,7 Z',
                    'fillcolor': 'rgba(255, 140, 184, 0.5)',
                    'line': {
                        'color': 'rgb(255, 140, 184)',
                    }
                },
            ]
        )
    }

if __name__ == '__main__':
    app.run_server()
like image 297
rahlf23 Avatar asked Dec 27 '18 23:12

rahlf23


1 Answers

From what I understood from looking around, there is not a way of doing this using only plotly-dash. What you need to achieve is essentially "responsive" typography, a topic which has some very interesting articles written about it:

  • https://css-tricks.com/books/volume-i/scale-typography-screen-size/
  • https://css-tricks.com/snippets/css/fluid-typography/
  • font-sizing using em units: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#Ems

Dash offers the option to override the default CSS and JS of an app and you can see from the layout documentation, how to use even external CSS files.

My suggestion: Research the resulting page to find how the text annotations get tagged (id and/or class) and use the methods provided in the articles above to create a satisfying result. (I am sorry that I cannot really do much more than suggesting, but I have not seen an example of your code :/)


EDIT (after comment and question editing):

So since the text annotations have the class textpoint we are going to override the default CSS:

  1. Create the suggested folder structure:

    - app.py
    - assets/
    |--- typography.css
    
  2. On the typography.css apply one of the methods mentioned above (I am going by the 2nd bulleted example):

    .textpoint{
        font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300)));
    }
    

EDIT #2:

I found the problem and a workaround to it:

  • The method to add custom CSS on Dash version >= 0.22 is indeed the above and it works.
  • The element that we need to target in order to apply our custom rule to the annotations is a text tag inside the .textpoint class (CSS syntax: .textpoint text)
  • For some reason, the custom CSS rule gets overridden (probably for a similar reason, explained on the top answer of this SO post: custom css being overridden by bootstrap css). So we need the "nuclear" option of !important.

Utilizing the above, the typography.css should look like this:

.textpoint text{
  font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300))) !important;
}
like image 193
John Moutafis Avatar answered Nov 17 '22 08:11

John Moutafis