You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Plotly高效绘制线段?优化单线段单Trace的实现方式

Plotly 线段绘制优化:避免单线段对应独立Trace

现有代码能实现线段绘制,但每条线段都创建独立的Scatter Trace,当数据量大时会导致性能下降,且不够高效。如何优化实现,减少Trace数量?

原代码如下:

import pandas as pd
import numpy as np

import plotly.graph_objects as go
from plotly.subplots import make_subplots

def plot_segments(df):    
    xname = "ts"
    yname = "duration"
    dfg = df.groupby('name')

    fig = go.Figure()
    colors=['#4f81bd','#c0504d','#9bbb59','#8064a2','#4bacc6','#f79646','#0000ff']
    traces = []
    dy = 1.1
    for i,[gname, df] in enumerate(dfg):
        for index,row in df.iterrows():
            x1 = row['ts']
            x2 = x1 + pd.to_timedelta(row['duration'],unit = 's')

            x = [x1,x2]
            y = [dy,dy]
            trace1 = go.Scatter(
                x=x,
                y=y,
                mode='lines+markers',            
                marker=dict(
                    size=4,
                    line=dict(width=0,color=colors[i])),
                line=dict(width=1,color=colors[i]),
            )
            traces.append(trace1)

        dy += .03

    fig.add_traces(traces)
    
    fontsize = 10
    fig.add_annotation(dict(
        font=dict(color="black",size=fontsize),
        x=0.5,
        xshift=0,
        y=0,
        yshift=-30,
        showarrow=False,
        text='Timestamp',
        textangle=0,
        xref="paper",
        yref="paper",
        xanchor='center',
        yanchor='top',
    ))
    
    fig.add_annotation(dict(
        font=dict(color="black",size=fontsize),
        x=-0,
        xshift=-20,
        y=0.5,
        showarrow=False,
        text='Category',
        textangle=-90,
        xref="paper",
        yref="paper",
        xanchor='right',
        yanchor='middle',
    ))

    xpading=.05
    fig.update_layout(
        margin=dict(l=50,t=40,r=10,b=40),
        plot_bgcolor='#ffffff',#'rgb(12,163,135)',
        paper_bgcolor='#ffffff',        
        title="Segments",
        #xaxis2_title="Timestamp",
        #yaxis_title="Interval(secs)",
        title_x=0.5,
        showlegend=False,
        legend=dict(x=.02,y=1.05),        
        barmode='group',
        bargap=0.05,
        bargroupgap=0.0,
        font=dict(
            family="Courier New, monospace",
            size=fontsize,
            color="black"
        ),
        xaxis=dict(
            visible=True,            
            tickangle=-15,            
            tickformat = '%m-%d %H:%M:%S',#datetime format
            showline=True,
            linecolor='black',
            color='black',
            linewidth=.5,
            ticks='outside',
            showgrid=False,
            gridcolor='grey',
            gridwidth=.5,
            griddash='solid',#'dot',            
        ),
        yaxis=dict(            
            range=[0,1.2],            
            showline=True,
            linecolor='black',
            color='black',
            linewidth=.5,            
            showgrid=True,
            gridcolor='grey',
            gridwidth=.5,
            griddash='solid',#'dot',
            zeroline=True,
            zerolinecolor='grey',
            zerolinewidth=.5,
            showticklabels=True,
        ),        
    )

    fig.show()
    return

data = [
    ['04-21 20:54:10.247','A',2],
    ['04-21 20:54:15.247','A',1],
    ['04-21 20:54:20.247','A',2],
    ['04-21 20:54:25.247','A',1],
    ['04-21 20:54:11.247','B',2],
    ['04-21 20:54:26.247','B',1],
    ['04-21 20:54:31.247','B',2],
    ['04-21 20:54:36.247','B',1]
    ]


df = pd.DataFrame(data,columns=['ts','name','duration'])

df['ts'] = pd.to_datetime(df['ts'],format="%m-%d %H:%M:%S.%f")
plot_segments(df)

原效果:
线段绘制效果


优化方案:同一类别共用一个Trace

核心思路:同一分类(name)的所有线段合并到同一个Scatter Trace中,通过在不同线段之间插入None实现线段分隔,Plotly会自动识别None并断开线段,这样每个分类仅需一个Trace,大幅减少Trace数量,提升渲染性能。

优化后的代码:

import pandas as pd
import numpy as np
import plotly.graph_objects as go

def plot_segments_optimized(df):    
    xname = "ts"
    yname = "duration"
    dfg = df.groupby('name')

    fig = go.Figure()
    colors=['#4f81bd','#c0504d','#9bbb59','#8064a2','#4bacc6','#f79646','#0000ff']
    dy = 1.1

    for i, (gname, df_group) in enumerate(dfg):
        x_vals = []
        y_vals = []
        for _, row in df_group.iterrows():
            x1 = row['ts']
            x2 = x1 + pd.to_timedelta(row['duration'], unit='s')
            # 添加当前线段的两个端点
            x_vals.extend([x1, x2])
            y_vals.extend([dy, dy])
            # 插入None分隔不同线段
            x_vals.append(None)
            y_vals.append(None)
        # 移除最后多余的None(可选,Plotly也可兼容)
        if x_vals:
            x_vals.pop()
            y_vals.pop()
        
        # 为当前类别创建单个Trace
        trace = go.Scatter(
            x=x_vals,
            y=y_vals,
            mode='lines+markers',            
            marker=dict(
                size=4,
                line=dict(width=0, color=colors[i])),
            line=dict(width=1, color=colors[i]),
            name=gname  # 可选,用于图例显示类别
        )
        fig.add_trace(trace)

        dy += .03

    fontsize = 10
    fig.add_annotation(dict(
        font=dict(color="black", size=fontsize),
        x=0.5,
        xshift=0,
        y=0,
        yshift=-30,
        showarrow=False,
        text='Timestamp',
        textangle=0,
        xref="paper",
        yref="paper",
        xanchor='center',
        yanchor='top',
    ))
    
    fig.add_annotation(dict(
        font=dict(color="black", size=fontsize),
        x=0,
        xshift=-20,
        y=0.5,
        showarrow=False,
        text='Category',
        textangle=-90,
        xref="paper",
        yref="paper",
        xanchor='right',
        yanchor='middle',
    ))

    fig.update_layout(
        margin=dict(l=50, t=40, r=10, b=40),
        plot_bgcolor='#ffffff',
        paper_bgcolor='#ffffff',        
        title="Segments (Optimized)",
        title_x=0.5,
        showlegend=True,  # 可选,开启图例查看类别
        legend=dict(x=.02, y=1.05),        
        font=dict(
            family="Courier New, monospace",
            size=fontsize,
            color="black"
        ),
        xaxis=dict(
            visible=True,            
            tickangle=-15,            
            tickformat='%m-%d %H:%M:%S',
            showline=True,
            linecolor='black',
            color='black',
            linewidth=.5,
            ticks='outside',
            showgrid=False,
            gridcolor='grey',
            gridwidth=.5,
            griddash='solid',            
        ),
        yaxis=dict(            
            range=[0, 1.2],            
            showline=True,
            linecolor='black',
            color='black',
            linewidth=.5,            
            showgrid=True,
            gridcolor='grey',
            gridwidth=.5,
            griddash='solid',
            zeroline=True,
            zerolinecolor='grey',
            zerolinewidth=.5,
            showticklabels=True,
        ),        
    )

    fig.show()
    return

data = [
    ['04-21 20:54:10.247','A',2],
    ['04-21 20:54:15.247','A',1],
    ['04-21 20:54:20.247','A',2],
    ['04-21 20:54:25.247','A',1],
    ['04-21 20:54:11.247','B',2],
    ['04-21 20:54:26.247','B',1],
    ['04-21 20:54:31.247','B',2],
    ['04-21 20:54:36.247','B',1]
    ]

df = pd.DataFrame(data,columns=['ts','name','duration'])
df['ts'] = pd.to_datetime(df['ts'],format="%m-%d %H:%M:%S.%f")
plot_segments_optimized(df)

优化说明

  1. 性能提升:原代码8条线段对应8个Trace,优化后2个类别对应2个Trace,数据量越大,渲染效率提升越明显。
  2. 视觉一致性:通过None分隔线段,最终呈现的视觉效果与原代码完全一致。
  3. 可扩展性:新增类别时仅需补充对应颜色,核心逻辑无需修改,维护成本更低。

内容的提问来源于stack exchange,提问作者lucky1928

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:32:01