【问题标题】:Select and delete data points in plotly dash 3d scatter在 plotly dash 3d scatter 中选择和删除数据点
【发布时间】:2020-02-07 03:33:43
【问题描述】:

我正在尝试将交互性添加到我正在使用破折号托管的绘图 3d 散点图中。我的问题包含两个相关部分:

(i) 我想在我的 3d 散点图中手动选择点并将所选点的颜色更改为红色。选择应该包括点击事件和选择事件。

(ii) 我希望能够在按下某个键时从图中删除这些点,例如“删除”键。

第 (i) 部分类似于绘图指南 https://plot.ly/python/click-events/ 中的示例,但是,on_click 不能用作 3d 散点图的方法。

我一直在尝试使用 FigureWidget,因为它显然提供了捕获点击和选择的方法,但是 2 天后我正在努力取得进展。

示例数据(将下面复制到剪贴板并运行 df = pd.read_clipboard(sep=','))

id,x_val,z_val,y_val
a,11.313449728149418,0.13039110880256777,0.5386387766748618
b,11.321463427315383,0.2360697833061771,1.32441455152796
c,10.127132005050942,0.23085014016641864,1.0961116175427044
d,11.639819269465233,0.0958798324712593,0.6506370305953094
e,8.892696370438149,0.08223988244819926,0.6440321391968353
f,6.711586646011124,0.3657515974938044,
g,7.095030650760687,,0.5723062047617504
h,6.4523124528415,,1.293852184258803
i,7.165105300812886,0.4151365420301895,-0.5920674079031845
j,7.480703395137295,0.14284429977557123,1.0600936940126982
k,5.570775744372319,,
l,4.358946555449826,,

我有下面的示例代码,我希望它几乎在那里(但不完全)。这个想法是“handle_click”应该捕获点击事件。我还应该处理“选择”事件,尽管我不确定如何执行此操作,因为 3d 散点图不提供选择框或套索工具。通过回调,我什至不知道如何启动,因为没有可以使用的带有 3d 散点图的 clickData/selectionData 事件(所以[Input('subindustry-dropdown', 'value')]) 不正确,请注意 subindustry-dropdown 不是我提供的示例,但我从下拉列表中选择我的 id,该下拉列表在我的开发版本中返回子行业值。)

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

import plotly.graph_objs as go
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.css.append_css({
    "external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"
})

app.layout = html.Div([html.Div(id = '3d-scatter'),
])


# Calculate and store data
@app.callback(Output('3d-scatter', 'children'),
               [Input('subindustry-dropdown', 'value')])
def chart_3d():          
    f = go.FigureWidget(px.scatter_3d(df, x = 'x_val', y = 'y_val', z = 'z_val', hover_name = 'company_nm'))

    f.layout.clickmode = 'event+select'
    f.data[0].on_click(handle_click) # if click, then update point/df.      

    return dcc.Graph(id = '3d_scat', figure=f)

def handle_click(trace, points, selector):
    c = list(f.data[0].marker.color)
    s = list(f.data[0].marker.size)
    for i in points.point_inds:
        c[i] = '#bae2be'
        s[i] = 20
        with f.batch_update():
            f.data[0].marker.color = c
            f.data[0].marker.size = s
    return f.data[0]

【问题讨论】:

    标签: python plotly-dash scatter3d


    【解决方案1】:

    这是一个允许的解决方案:

    • 通过单独单击选择点

    • 按html按钮删除选中的点

    • 按 html 按钮清除选择

    根据issue,目前不支持在 3d 图中选择多个点

    (使用FigureWidget好像没什么区别,所以我去掉了)

    import dash
    import plotly.express as px
    import dash_core_components as dcc
    import dash_html_components as html
    from dash.dependencies import Input, Output, State
    
    import plotly.graph_objs as go
    import pandas as pd
    import json
    
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
    
    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    app.css.append_css({
        "external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"
    })
    
    
    
    import pandas as pd
    df = pd.DataFrame(columns=['x_val','z_val','y_val'])
    df.loc['a'] = [11.313449728149418,0.13039110880256777,0.5386387766748618]
    df.loc['b'] = [11.321463427315383,0.2360697833061771,1.32441455152796]
    df.loc['c'] = [10.127132005050942,0.23085014016641864,1.0961116175427044]
    df.loc['d'] = [11.639819269465233,0.0958798324712593,0.6506370305953094]
    df.loc['e'] = [8.892696370438149,0.08223988244819926,0.6440321391968353]
    df.loc['f'] = [6.711586646011124,0.3657515974938044,0]
    df.loc['g'] = [7.095030650760687,0,0.5723062047617504]
    df.loc['h'] = [6.4523124528415,0,1.293852184258803]
    df.loc['i'] = [7.165105300812886,0.4151365420301895,-0.5920674079031845]
    df.loc['j'] = [7.480703395137295,0.14284429977557123,1.0600936940126982]
    df.loc['k'] = [5.570775744372319,0,0]
    df.loc['l'] = [4.358946555449826,0,0]
    
    def create_figure(skip_points=[]):
        dfs = df.drop(skip_points)
        return px.scatter_3d(dfs, x = 'x_val', y = 'y_val', z = 'z_val')
    f= create_figure()
    
    app.layout = html.Div([html.Button('Delete', id='delete'),
                        html.Button('Clear Selection', id='clear'),
                        dcc.Graph(id = '3d_scat', figure=f),
                        html.Div('selected:'),
                        html.Div(id='selected_points'), #, style={'display': 'none'})),
                        html.Div('deleted:'),
                        html.Div(id='deleted_points') #, style={'display': 'none'}))
    ])
    
    @app.callback(Output('deleted_points', 'children'),
                [Input('delete', 'n_clicks')],
                [State('selected_points', 'children'),
                State('deleted_points', 'children')])
    def delete_points(n_clicks, selected_points, delete_points):
        print('n_clicks:',n_clicks)
        if selected_points:
            selected_points = json.loads(selected_points)
        else:
            selected_points = []
    
        if delete_points:
            deleted_points = json.loads(delete_points)
        else:
            deleted_points = []
        ns = [p['pointNumber'] for p in selected_points]
        new_indices = [df.index[n] for n in ns if df.index[n] not in deleted_points]
        print('new',new_indices)
        deleted_points.extend(new_indices)
        return json.dumps(deleted_points)
    
    
    
    @app.callback(Output('selected_points', 'children'),
                [Input('3d_scat', 'clickData'),
                    Input('deleted_points', 'children'),
                    Input('clear', 'n_clicks')],
                [State('selected_points', 'children')])
    def select_point(clickData, deleted_points, clear_clicked, selected_points):
        ctx = dash.callback_context
        ids = [c['prop_id'] for c in ctx.triggered]
    
        if selected_points:
            results = json.loads(selected_points)
        else:
            results = []
    
    
        if '3d_scat.clickData' in ids:
            if clickData:
                for p in clickData['points']:
                    if p not in results:
                        results.append(p)
        if 'deleted_points.children' in ids or  'clear.n_clicks' in ids:
            results = []
        results = json.dumps(results)
        return results
    
    @app.callback(Output('3d_scat', 'figure'),
                [Input('selected_points', 'children'),
                Input('deleted_points', 'children')],
                [State('deleted_points', 'children')])
    def chart_3d( selected_points, deleted_points_input, deleted_points_state):
        global f
        deleted_points = json.loads(deleted_points_state) if deleted_points_state else []
        f = create_figure(deleted_points)
    
        selected_points = json.loads(selected_points) if selected_points else []
        if selected_points:
            f.add_trace(
                go.Scatter3d(
                    mode='markers',
                    x=[p['x'] for p in selected_points],
                    y=[p['y'] for p in selected_points],
                    z=[p['z'] for p in selected_points],
                    marker=dict(
                        color='red',
                        size=5,
                        line=dict(
                            color='red',
                            width=2
                        )
                    ),
                    showlegend=False
                )
            )
    
        return f
    
    if __name__ == '__main__':
        app.run_server(debug=True)
    

    【讨论】:

    • 谢谢。你知道如何一次选择多个点吗?与 dash 2d 散点图不同,没有可用的框/套索选择。
    • 谢谢卡尔 :) 我的第一个赏金!根据链接,我还发布了 3d 不支持框/套索...稍后在我看来,您可以做的是删除具有功能的(或多个)选定点周围的圆形/框形体积中的点你用另一个 html 按钮触发...
    猜你喜欢
    • 1970-01-01
    • 2020-01-09
    • 1970-01-01
    • 2022-01-08
    • 2021-10-16
    • 1970-01-01
    • 2022-07-01
    • 1970-01-01
    • 2021-11-16
    相关资源
    最近更新 更多