访问可编辑的注释

时间:2019-02-12 17:10:13

标签: python plotly-dash

我有一个简单的Dash应用程序,其绘图可以在单击“更新”按钮时进行更新。我在此图上添加了可编辑的注释。我希望能够访问用户在注释中键入的任何内容,以便在更新图形时,注释保持不变。我最想知道是否有一种方法可以访问他们的编辑方式。

我一直试图将当前注释保存到创建图形时可以访问的存储组件中。我试图制作一个“保存”按钮,将存储数据更改为当前注释文本。我唯一的猜测是,编辑注释时,新文本不会存储在默认文本所在的位置。那样的话,或者某件事只是在我头上,而我却没有意识到。

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, State, Output
import random

app = dash.Dash(__name__)
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div(
    id = 'top_level',
    children=[
        html.Div(
            id='plot_div',
            children=[],
        ),

        html.Button(
            id = 'update',
            children="Update",
        ),

        dcc.Store(
            id='annotation_storage',
            data='Editable Annotation',
        )
    ]
)
@app.callback(
    Output('plot_div', 'children'),
    [Input('update', 'n_clicks')],
    [State('annotation_storage', 'data')]
)
def plot_update(clicked, annotation):
    if clicked:
        x = random.sample(range(1,101), 20)
        y = random.sample(range(1,101), 20)
        figure = {'data': [{'x': x, 'y': y, 
                            'type': 'scatter', 'mode': 'markers'}],
                  'layout': {
                      'xaxis': {
                          'fixedrange': True,
                          'zeroline': False,
                      },
                      'yaxis': {
                          'fixedrange': True,
                          'zeroline': False,
                      },
                      'annotations': [{
                            'x': -0.05,
                            'y': 0.5,
                            'showarrow':False,
                            'text':annotation,
                            'xref':'paper',
                            'yref':'paper',
                            }],
                        }
                  }
        return [dcc.Graph(
            id = 'plot_output',
            figure = figure,
            config = {
                'editable': True,
                'edits': {
                    'axisTitleText': False,
                    'titleText': False,
                },
                'displayModeBar': False,
            },
        ),
        html.Button(
            id = 'save_annotation',
            children='Save',
        ),
        ]

@app.callback(
    Output('annotation_storage', 'data'),
    [Input('save_annotation', 'n_clicks')],
    [State('plot_output', 'figure')]
)
def save_annotation(clicked, figure):
    if clicked:
        annotation = figure['layout']['annotations'][0]['text']
        return annotation
if __name__ == '__main__':
    app.run_server(debug=True, port=1000)

当前,在图形更新时,即使用户对其进行编辑,注释也只是恢复为默认文本“可编辑注释”。我希望注释即使在图形更新时也保持不变。任何见解将不胜感激!谢谢!

2 个答案:

答案 0 :(得分:0)

Dash user guide中所述:

  

dcc.Graph组件具有四个可以通过更改的属性   用户互动:hoverDataclickDataselectedDatarelayoutData

这意味着您与用户互动时,您使用的figure属性不会更新。

要解决您的问题,请将回调函数更新为使用relayoutData而不是figure

@app.callback(
    Output('annotation_storage', 'data'),
    [Input('save_annotation', 'n_clicks')],
    [State('plot_output', 'relayoutData')]
)
def save_annotation(clicked, relayout_data):
    if clicked:
        annotation = relayout_data['annotations[0].text']
        return annotation

您可以在调试模式下运行您的应用程序,并在回调函数内部添加一个断点,以探索上述四个属性给出的值。这就是我在relayout_data内查找注释文本的键的目的。

答案 1 :(得分:0)

fig = px.scatter(df, x="x", y="y")
## TODO - load init annotations to fig.layout.annotations


dash_app1.layout = html.Div([
    html.H1('Hi there, I am dashboard1'),
    dcc.Graph(
        id='example-graph',
        figure=fig,
        config={
            'editable': True,
            'edits': {
                'axisTitleText': False,
                'titleText': False,
            },
        }
    )
    ])

@dash_app1.callback(
    Output('example-graph', 'figure'),
    [Input('example-graph', 'clickData'),
     Input('example-graph', 'relayoutData')],
    prevent_initial_call=True)
def annotation(clickData, relayoutData):
    if clickData:
        # get click xy
        point = clickData["points"][0]
        # if not exist, add
        if f"'x': {point['x']}, 'y': {point['y']}" not in str(fig.layout.annotations):
            fig.add_annotation(x=point["x"], y=point["y"], text="Click Here To Edit")
            print("Add: ", fig.layout.annotations)
        else:
            # if exist, edit
            if relayoutData:
                fig.plotly_relayout(relayoutData)
                print("Edit: ", fig.layout.annotations)

    ## TODO - save fig.layout.annotation
    return fig

Shovalt的回答很好,感谢您的启发。我使用clickData事件添加注释,并使用relayoutData事件记录编辑和拖动位置。此更新可以简单地通过fig.plotly_relayout完成,并且不包含在文档中。我利用了JS的优势。老实说,Plotly Dash文档不足以对开发人员友好,特别是对于像我这样的初学者而言。确保您在IDE中启用了代码补全功能,有时它比文档更有用...

此外,dcc.Store在这里有点重复,因为您可以在fig.layout.annotations中找到所有注释。注意dcc.Store,它有一个内存限制,如文档所述:It's generally safe to store up to 2MB in most environments, and 5~10MB in most desktop-only applications.

更新:

我发现此relayoutData侦听器可能导致缩放不可用,但我认为现在可以使用此功能

@dash_app1.callback(
    Output('example-graph', 'figure'),
    [Input('example-graph', 'clickData'),
     Input('example-graph', 'relayoutData')],
    prevent_initial_call=True)
def annotation(clickData, relayoutData):
    print("Click")
    print(clickData)
    print("Relayout")
    print(relayoutData)
    if clickData:
        ## get click xy
        point = clickData["points"][0]
        ## if not exist, add
        if f"'x': {point['x']}, 'y': {point['y']}" not in str(fig.layout.annotations):
            fig.add_annotation(x=point["x"], y=point["y"], text="Click Here To Edit")
            print("Add: ", fig.layout.annotations)
            ## TODO - save fig.layout.annotations to SQLite
            return fig
    if relayoutData:
        ## initial loading or refreshing action
        if str(relayoutData) == "{'autosize': True}":
            return fig
        ## zooming action
        ## enable zooming after resetting zooming
        if 'xaxis.range' in str(relayoutData) and 'yaxis.range' in str(relayoutData):
            fig.layout.xaxis.autorange = False
            fig.layout.yaxis.autorange = False
        ## edit annotation position or text | also enable zooming after resetting
        fig.plotly_relayout(relayoutData)
        print("Edit: ", fig.layout.annotations) 
        ## TODO - If edit, then update fig.layout.annotations to SQLite
    return fig