2

I am trying to create an animated 3D scatterplot to represent fish swimming in 3D space. I have 8 fish, and for each fish I have 4 points. I am able to make the graph and animate it, however the size of the graph changes randomly between time points. I have set the axes mins and maxes, but the distance between them seems to change. What aspect of the plot do I need to alter in order to keep it stable?

This is the plotly express command that I am using:

fig = px.scatter_3d(df,x="x", y="y", z="z",
                       color="Fish", animation_frame="Frame", hover_data = ["BodyPart"],
                       range_x=[-0.25,0.25], range_y=[-0.15,0.15], range_z=[-0.15,0.15], 
                       color_continuous_scale = "rainbow")

These two images show the graph one frame apart from one another. The green square shows stats on one point to show that it is not changing drastically: image1 of fish image2 of fish

I am also including this video for a clearer example.

Edited:

Minimum graphing code:

import pandas as pd
import plotly.express as px

data_dict = {'Fish': {0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1, 8: 2, 9: 2, 10: 2, 11: 2, 12: 3, 13: 3, 14: 3, 15: 3, 16: 4, 17: 4, 18: 4, 19: 4, 20: 5, 21: 5, 22: 5, 23: 5, 24: 6, 25: 6, 26: 6, 27: 6, 28: 7, 29: 7, 30: 7, 31: 7, 32: 0, 33: 0, 34: 0, 35: 0, 36: 1, 37: 1, 38: 1, 39: 1, 40: 2, 41: 2, 42: 2, 43: 2, 44: 3, 45: 3, 46: 3, 47: 3, 48: 4, 49: 4, 50: 4, 51: 4, 52: 5, 53: 5, 54: 5, 55: 5, 56: 6, 57: 6, 58: 6, 59: 6, 60: 7, 61: 7, 62: 7, 63: 7}, 'BodyPart': {0: 'head', 1: 'midline2', 2: 'tailbase', 3: 'tailtip', 4: 'head', 5: 'midline2', 6: 'tailbase', 7: 'tailtip', 8: 'head', 9: 'midline2', 10: 'tailbase', 11: 'tailtip', 12: 'head', 13: 'midline2', 14: 'tailbase', 15: 'tailtip', 16: 'head', 17: 'midline2', 18: 'tailbase', 19: 'tailtip', 20: 'head', 21: 'midline2', 22: 'tailbase', 23: 'tailtip', 24: 'head', 25: 'midline2', 26: 'tailbase', 27: 'tailtip', 28: 'head', 29: 'midline2', 30: 'tailbase', 31: 'tailtip', 32: 'head', 33: 'midline2', 34: 'tailbase', 35: 'tailtip', 36: 'head', 37: 'midline2', 38: 'tailbase', 39: 'tailtip', 40: 'head', 41: 'midline2', 42: 'tailbase', 43: 'tailtip', 44: 'head', 45: 'midline2', 46: 'tailbase', 47: 'tailtip', 48: 'head', 49: 'midline2', 50: 'tailbase', 51: 'tailtip', 52: 'head', 53: 'midline2', 54: 'tailbase', 55: 'tailtip', 56: 'head', 57: 'midline2', 58: 'tailbase', 59: 'tailtip', 60: 'head', 61: 'midline2', 62: 'tailbase', 63: 'tailtip'}, 'x': {0: 0.121283071, 1: 0.074230535, 2: 0.096664814, 3: 0.063435668, 4: -0.11843468, 5: -0.133776416, 6: -0.12698166, 7: -0.133996648, 8: 0.154499401, 9: 0.099541555, 10: 0.126525899, 11: 0.086448979, 12: -0.001723707, 13: -0.064203743, 14: -0.033163578, 15: -0.077987938, 16: 0.160456072, 17: 0.175340028, 18: 0.178537856, 19: 0.16438273, 20: -0.151890354, 21: -0.099510254, 22: -0.123827166, 23: -0.08765671, 24: 0.052741099, 25: -0.003778201, 26: 0.022010701, 27: -0.014747641, 28: -0.137528989, 29: -0.078632593, 30: -0.106688178, 31: -0.065274018, 32: 0.12128202, 33: 0.074230379, 34: 0.096662597, 35: 0.063435699, 36: -0.118412987, 37: -0.133729238, 38: -0.12729935, 39: -0.134238167, 40: 0.154498856, 41: 0.099541572, 42: 0.126525899, 43: 0.086450612, 44: -0.001719156, 45: -0.064209291, 46: -0.033163578, 47: -0.07796947, 48: 0.157094899, 49: 0.175288008, 50: 0.178383788, 51: 0.1643551, 52: -0.153086656, 53: -0.100645272, 54: -0.125700666, 55: -0.089248865, 56: 0.052731775, 57: -0.003778201, 58: 0.022011924, 59: -0.014749184, 60: -0.138954183, 61: -0.079588201, 62: -0.107413558, 63: -0.06588028}, 'y': {0: -0.018777537, 1: -0.017936625, 2: -0.019031854, 3: -0.018688299, 4: 0.031655295, 5: 0.089278103, 6: 0.060434868, 7: 0.102354879, 8: 0.012448659, 9: 0.005374916, 10: 0.008431857, 11: 0.010384436, 12: 0.007394437, 13: 0.002657548, 14: 0.0047918, 15: 0.004216939, 16: -0.061691249, 17: -0.022574622, 18: -0.044862196, 19: -0.015288812, 20: 0.126254494, 21: 0.125420316, 22: 0.127216595, 23: 0.122366769, 24: -0.018798237, 25: -0.026209512, 26: -0.020654802, 27: -0.030922742, 28: 0.100460973, 29: 0.091726762, 30: 0.095608508, 31: 0.089022071, 32: -0.018930378, 33: -0.018313362, 34: -0.019121954, 35: -0.018839649, 36: 0.030465513, 37: 0.087966041, 38: 0.058855924, 39: 0.100617287, 40: 0.012372615, 41: 0.00530059, 42: 0.008431857, 43: 0.009864426, 44: 0.007169236, 45: 0.002524294, 46: 0.0047918, 47: 0.002813216, 48: -0.061409007, 49: -0.024774863, 50: -0.045825365, 51: -0.017002469, 52: 0.125813664, 53: 0.125533354, 54: 0.126988948, 55: 0.121414741, 56: -0.019165739, 57: -0.026209512, 58: -0.020802186, 59: -0.031842627, 60: 0.100213119, 61: 0.091677506, 62: 0.095490242, 63: 0.08724155}, 'z': {0: -0.011584533, 1: -0.005671144, 2: -0.004720913, 3: -0.007099159, 4: 0.048633092, 5: 0.044680886, 6: 0.047755313, 7: 0.047602698, 8: 0.005219131, 9: 0.020195691, 10: 0.013766486, 11: 0.019271016, 12: -0.009086866, 13: 0.005213358, 14: -0.003552202, 15: 0.001820855, 16: -0.039992723, 17: 0.041166976, 18: -0.013040119, 19: 0.048827692, 20: 0.044577227, 21: 0.043492943, 22: 0.045104437, 23: 0.0399218, 24: 0.007934858, 25: 0.007980119, 26: 0.010593472, 27: 0.006390279, 28: 0.070277892, 29: 0.066889416, 30: 0.070485941, 31: 0.054907996, 32: -0.011559485, 33: -0.005583401, 34: -0.004725084, 35: -0.007089815, 36: 0.048823811, 37: 0.04574317, 38: 0.047201689, 39: 0.043995531, 40: 0.005234299, 41: 0.020211407, 42: 0.013766486, 43: 0.019405438, 44: -0.009034049, 45: 0.005200504, 46: -0.003552202, 47: 0.002061042, 48: -0.035258171, 49: 0.041424053, 50: -0.013317812, 51: 0.048629332, 52: 0.043972705, 53: 0.042581942, 54: 0.046299595, 55: 0.040028712, 56: 0.007931264, 57: 0.007980119, 58: 0.010624531, 59: 0.006616644, 60: 0.068992196, 61: 0.064455916, 62: 0.07226277, 63: 0.056393304}, 'Frame': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0, 21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 26: 0, 27: 0, 28: 0, 29: 0, 30: 0, 31: 0, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1}}

df = pd.DataFrame(data_dict)

fig = px.scatter_3d(df,x="x", y="y", z="z", color="Fish", animation_frame="Frame", hover_data = ["BodyPart"],
                        range_x=[-0.25,0.25], range_y=[-0.15,0.15], range_z=[-0.15,0.15], color_continuous_scale = "rainbow")

fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))

fig.show()
Squidswell
  • 87
  • 1
  • 7
  • @Squidswll It simply seems that `px.scatter3D` tried to make each frame in your animation more visually pleasing by adjusting the length of the axes depending on the structure of the data of that particular frame. If you share a complete code snippet to reproduce your figure together with a [sample of your data](https://stackoverflow.com/questions/63163251/pandas-how-to-easily-share-a-sample-dataframe-using-df-to-dict/63163254#63163254) then I'm sure you'll quickly get the assistance you seek. – vestland Sep 28 '21 at 17:13
  • 1
    Thank you for the feedback. I've added more code and the first two frames of data – Squidswell Sep 28 '21 at 17:43

1 Answers1

1

This seems related to the aspectratio in fig.layout.scene:

layout.Scene({
    'aspectmode': 'auto',
    'aspectratio': {'x': 1.7359689116422856, 'y': 0.9924641251101735, 'z':0.5804211635071164},

If you manually set x, y and z in the dict above to something specific, the flinching of the figure between animation frames seems to disappear.

I've tried:

fig.layout.scene.aspectratio = {'x':1, 'y':1, 'z':1}
fig.show()

And the results are promising. Give it a go on your end and let me know how it works out for you.


It also seems, as you've already discovered, to work best in tandem with setting defined ranges for x_range, y_range, z_range. Since your datasample is a bit limited, I've been messing around with px.data.gapminder().

Plot

enter image description here

Complete code

import plotly.express as px
df = px.data.gapminder()
# df
fig = px.scatter_3d(df, x = 'pop', y='lifeExp', z = 'gdpPercap', animation_frame='year',
                    range_x=[int(df['pop'].min()*0.5),int(df['pop'].max()*1.5)],
                    range_y=[int(df.lifeExp.min()*0.5),int(df.lifeExp.max()*1.5)],
                    range_z=[int(df['gdpPercap'].min()*0.5),int(df['gdpPercap'].max()*1.5)]
                   )
fig.layout.scene.aspectratio = {'x':1, 'y':1, 'z':1}
fig.show()
vestland
  • 55,229
  • 37
  • 187
  • 305