Search code examples
pythonplotly

Play frames back in real time based on animation_frame


I have a simple figure like this:

fig = px.scatter(
    x=list(map(lambda x: x["posX"], data["events"])),
    y=list(map(lambda x: x["posY"], data["events"])),
    range_x=[0, data["width"]],
    range_y=[0, data["height"]],
    animation_frame=list(map(lambda x: x["timestamp"], data["events"]))
)

for movement in data["events"]:
    fig.add_trace(
        go.Scatter(
            mode="markers",
            x=[movement["posX"]],
            y=[movement["posY"]],
            marker=dict(
                size=1,
                opacity=0.5,
                line=dict(
                    color="LightBlue",
                    width=8
                )
            )
        )
    )

using the following sample data:

{
    "events": [
        {
            "posX": 478,
            "posY": 454,
            "timestamp": 16215.800000000745
        },
        {
            "posX": 486,
            "posY": 456,
            "timestamp": 16229.5
        },
        {
            "posX": 529,
            "posY": 470,
            "timestamp": 16251.400000002235
        },
        {
            "posX": 567,
            "posY": 483,
            "timestamp": 16262.60000000149
        },
        {
            "posX": 644,
            "posY": 505,
            "timestamp": 16285.300000000745
        },
        {
            "posX": 679,
            "posY": 514,
            "timestamp": 16296.400000002235
        },
        {
            "posX": 783,
            "posY": 536,
            "timestamp": 16317.5
        },
        {
            "posX": 856,
            "posY": 550,
            "timestamp": 16330.60000000149
        },
        {
            "posX": 987,
            "posY": 575,
            "timestamp": 16352.400000002235
        },
        {
            "posX": 1036,
            "posY": 583,
            "timestamp": 16363.5
        },
        {
            "posX": 1138,
            "posY": 599,
            "timestamp": 16385.5
        },
        {
            "posX": 1183,
            "posY": 603,
            "timestamp": 16397
        },
        {
            "posX": 1219,
            "posY": 604,
            "timestamp": 16418.400000002235
        },
        {
            "posX": 1230,
            "posY": 604,
            "timestamp": 16430
        },
        {
            "posX": 1235,
            "posY": 603,
            "timestamp": 16453.10000000149
        },
        {
            "posX": 1236,
            "posY": 602,
            "timestamp": 16465.5
        },
        {
            "posX": 1237,
            "posY": 601,
            "timestamp": 16487.60000000149
        },
        {
            "posX": 1237,
            "posY": 601,
            "timestamp": 16499.199999999255
        },
        {
            "posX": 1233,
            "posY": 597,
            "timestamp": 16519.60000000149
        },
        {
            "posX": 1230,
            "posY": 595,
            "timestamp": 16531.10000000149
        },
        {
            "posX": 1229,
            "posY": 593,
            "timestamp": 16553.300000000745
        },
        {
            "posX": 1229,
            "posY": 593,
            "timestamp": 16577.60000000149
        },
        {
            "posX": 1230,
            "posY": 593,
            "timestamp": 16587.199999999255
        },
        {
            "posX": 1230,
            "posY": 593,
            "timestamp": 16598.5
        },
        {
            "posX": 1230,
            "posY": 593,
            "timestamp": 16632.300000000745
        },
        {
            "posX": 1229,
            "posY": 593,
            "timestamp": 16643.900000002235
        },
        {
            "posX": 1227,
            "posY": 593,
            "timestamp": 16654.699999999255
        },
        {
            "posX": 1225,
            "posY": 593,
            "timestamp": 16666.10000000149
        },
        {
            "posX": 1223,
            "posY": 592,
            "timestamp": 16679
        },
        {
            "posX": 1215,
            "posY": 590,
            "timestamp": 16701.300000000745
        },
        {
            "posX": 1206,
            "posY": 589,
            "timestamp": 16712.800000000745
        },
        {
            "posX": 1184,
            "posY": 587,
            "timestamp": 16735.400000002235
        },
        {
            "posX": 1171,
            "posY": 585,
            "timestamp": 16746.5
        },
        {
            "posX": 1148,
            "posY": 582,
            "timestamp": 16768.800000000745
        },
        {
            "posX": 1134,
            "posY": 580,
            "timestamp": 16780.199999999255
        },
        {
            "posX": 1107,
            "posY": 576,
            "timestamp": 16802.5
        },
        {
            "posX": 1093,
            "posY": 574,
            "timestamp": 16814
        },
        {
            "posX": 1069,
            "posY": 571,
            "timestamp": 16836.199999999255
        },
        {
            "posX": 1065,
            "posY": 570,
            "timestamp": 16847.800000000745
        },
        {
            "posX": 1060,
            "posY": 570,
            "timestamp": 16870.10000000149
        },
        {
            "posX": 1059,
            "posY": 570,
            "timestamp": 16881.699999999255
        },
        {
            "posX": 1057,
            "posY": 569,
            "timestamp": 16903.60000000149
        },
        {
            "posX": 1056,
            "posY": 569,
            "timestamp": 16914.699999999255
        },
        {
            "posX": 1053,
            "posY": 569,
            "timestamp": 16936.800000000745
        },
        {
            "posX": 1051,
            "posY": 569,
            "timestamp": 16949.10000000149
        },
        {
            "posX": 1048,
            "posY": 569,
            "timestamp": 16971.699999999255
        },
        {
            "posX": 1045,
            "posY": 568,
            "timestamp": 16982.60000000149
        },
        {
            "posX": 1038,
            "posY": 567,
            "timestamp": 17005.199999999255
        },
        {
            "posX": 1035,
            "posY": 567,
            "timestamp": 17016.60000000149
        },
        {
            "posX": 1032,
            "posY": 567,
            "timestamp": 17038
        },
        {
            "posX": 1032,
            "posY": 567,
            "timestamp": 17050.300000000745
        },
        {
            "posX": 1032,
            "posY": 567,
            "timestamp": 17072.60000000149
        },
        {
            "posX": 1032,
            "posY": 567,
            "timestamp": 17083.800000000745
        },
        {
            "posX": 1030,
            "posY": 566,
            "timestamp": 17105.699999999255
        },
        {
            "posX": 1028,
            "posY": 565,
            "timestamp": 17118.199999999255
        },
        {
            "posX": 1027,
            "posY": 565,
            "timestamp": 17138.400000002235
        }
    ],
    "destination": {
        "topLeft": [
            994,
            549.5
        ],
        "bottomRight": [
            1033.71875,
            571
        ]
    },
    "width": 1440,
    "height": 728
}

I want to be able to replay this data in order according to timestamp, which is in milliseconds.

I have the following code from this answer:

fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 1
fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 1

which updates it every millisecond. The problem is, it plays each frame every millisecond, instead of playing it back in real time based on animation_frame (which is the timestamp variable in each JSON array element).

How would I go about making it play back in real time, instead of every millisecond? timestamp is in milliseconds.


Solution

  • After experimenting with frame duration, and also discovering that frame can be an array of frames from Plotly's JavaScript documentation on animations, I ended up with this code that correctly plays the data in real time.

    fig.layout.updatemenus[0].buttons[0].args[1]["frame"] = [
        {
            # duration is how long the frame remains in this state
            # including the time spent transitioning.
            "duration": data["events"][i]["timestamp"] - data["events"][i-1]["timestamp"]
        }
    
        for i in range(1, len(data["events"]))
    ]
    

    From the documentation:

    The transition duration defines the amount of time spent interpolating a trace from one state to another (currently limited to scatter traces), while the frame duration defines the total time spent in that state, including time spent transitioning.

    The code uses list comprehension to calculate the difference from the current timestamp and the previous one, since in my case the timestamps are incremental.