Advertisement

如何在 Python 中使用 Plotly 创建太阳系的 3D 模型 (教程含源码)

阅读量:

Python中提供了丰富的图形库可供生成所有主要类型的图表,并通过这些库直观展示您的数据。然而您是否曾想过将它们用于更为复杂的情景?例如深入探索Plotly创建的三维和动画图形带来的独特效果?

请添加图片描述

我没有与 Plotly 任何关联。尽管如此,在过去一段时间内我一直都在 Python 中使用他们的库,并发现它们非常有成效。因此决定深入探究这一工具。

在这一场景中更直观地呈现,在此处更直观地呈现,在这里更直观地呈现,在这里更直观地呈现,在这里更直观地呈现

选择正确的情节

原本的目标是为了在Scatter3D绘图中简单地给太阳和行星定制大小标记。
然而这种做法并非最佳选择。
由于当缩放这些标记时它们的相对尺寸不变。
从而带来了不利的结果例如土星环位于行星内部一旦缩小太阳就会吞没所有的岩石内行星如图所示。

在这里插入图片描述

这表明我必须寻找一个不同的方法来展示太阳系中的行星。幸运的是,Plotly 支持绘制天体的三维曲面图。然而,在这种情况下需要绘制多个球体,并且每个球体会有不同的尺寸与色彩组合。

在这里插入图片描述

因此,在最终阶段我们所需的工具包括用于绘制轨道的 Plotly 散点图(Scatter3D)以及用于绘制球体的 Plotly 曲面图(Surface)。这两种图形将替代标记来构建我们的行星模型。

太阳和行星

为了尽可能减少所需编码量, 我们决定以函数形式创建一个球体. 这个解决方案相当简短, 然而其构造依赖于一定的球坐标系知识. 您可在此Wikipedia页面上查阅相关内容. 或者, 只需接受我们以下列方式定义球体的笛卡尔坐标系:

复制代码
    x = r cosθ sinφ ;
    y = r sinθ sinφ;
    z = r cosφ
    
    
      
      
      
    
    代码解读

那么,让我们看一下代码:

复制代码
    def spheres(size, clr, dist=0): 
    
    # Set up 100 points. First, do angles
    theta = np.linspace(0,2*np.pi,100)
    phi = np.linspace(0,np.pi,100)
    
    # Set up coordinates for points on the sphere
    x0 = dist + size * np.outer(np.cos(theta),np.sin(phi))
    y0 = size * np.outer(np.sin(theta),np.sin(phi))
    z0 = size * np.outer(np.ones(100),np.cos(phi))
    
    # Set up trace
    trace= go.Surface(x=x0, y=y0, z=z0, colorscale=[[0,clr], [1,clr]])
    trace.update(showscale=False)
    
    return trace
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

绘制一个球体时,我们需要提供包含空间点的x、y和z坐标数组。请注意,在此过程中,请您确定所需的点数。但若我们选择的点数量过少,则将导致绘制出的球体不够光滑。因此,在这种情况下,我们生成了两个等间距分布的一百个点。

一旦确定了theta和phi值后,在这里我们可以利用前述公式简便地计算出x、y和z坐标的具体数值。值得注意的是,在这里,“大小”替代了半径®符号,并特别指出这一距离参数实际上代表的是与太阳之间的距离。它指示了该点与太阳的距离数值。这样一来,则可将球体精确放置于所需位置。

另外,请注意该库要求z坐标轴是一个二维数组结构。为此,在实现过程中我们采用了以下方法:将第一个维度全部填充为1值;而第二个维度则按照cosφ的公式进行计算。

最后, 我们设定了一个"色调"参数, 它不仅是一个单一的颜色选项, 而且在默认情况下可能包含多个色彩组合以提供更为丰富的视觉效果(如上文所示, 图片中设置了两种主要色调)。然而, 在本例中, 我们希望每个对象都采用单一色调, 因此尽管仍需至少提供两个参数以完成配置任务, 但只需将相同的色调值分配给这两个参数即可完成配置

轨道

如果你凝视天空,则不会察觉到覆盖其中的轨道线

复制代码
    def orbits(dist, offset=0, clr='white', wdth=2): 
    
    # Initialize empty lists for each set of coordinates
    xcrd=[]
    ycrd=[]
    zcrd=[]
    
    # Calculate coordinates
    for i in range(0,361):
        xcrd=xcrd+[(round(np.cos(math.radians(i)),5)) * dist + offset]
        ycrd=ycrd+[(round(np.sin(math.radians(i)),5)) * dist]
        zcrd=zcrd+[0]
    
    trace = go.Scatter3d(x=xcrd, y=ycrd, z=zcrd, marker=dict(size=0.1), line=dict(color=clr,width=wdth))
    return trace
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

绘制轨道的作用变得更加简便了。它基本上只是一个圆形或者其他形状(如平圆),仅需基础性的几何学知识。

为了展示一种不同的工作流程,在编程中我们初始化了一个空列表,并通过for循环每次向该列表添加一个项目。值得注意的是,在计算圆上的点时将角度转换为弧度。同样地,在本例中'dist'代表太阳到行星的距离(即半径r),而'offset'则允许我们调整轨道中心的位置以便绘制围绕地球或其他天体的环状轨道。在这种情况下,默认情况下轨道中心应位于所研究行星的位置(例如对于土星来说是1433.5,0,0),而不是太阳的位置(0,0,0)。

至于颜色,我们简单地将所有轨道设为白色,以便与黑色空间形成对比。

注释

这个函数是我们必须定义的关键部分。这是一个基础性的功能,它允许我们在选定的坐标系中标注行星名称。特别值得注意的是,在这种情况下我们采用注释而非直接标记文本的方式是为了确保行星名称位于球体外部。

复制代码
    def annot(xcrd, zcrd, txt, xancr='center'):
    strng=dict(showarrow=False, x=xcrd, y=0, z=zcrd, text=txt, xanchor=xancr, font=dict(color='white',size=12))
    return strng
    
    
      
      
      
    
    代码解读

此处仅需指定希望文本出现x和z坐标的位置即可。
请注意,在设置锚点时有两种选择:
一种是给所有行星设置在它们"中心"位置与"左侧"边界的锚点;
另一种则是专门针对太阳的位置进行独特设置。
由于太阳体积庞大,在其"左侧"设定独特的锚定点有助于提升整体视觉效果。

绘制太阳系

现在我们收集并整理了各种辅助功能,在当前阶段我们可以通过整合这些辅助功能的数据信息……完成数据可视化。

复制代码
    # Note, true diameter of the Sun is 1,392,700km. Reduced it for better visualization
    diameter_km = [200000, 4878, 12104, 12756, 6787, 142796, 120660, 51118, 48600]
    # Modify planet sizes making them retative to the Earth size, where Earth in this case = 2
    diameter = [((i / 12756) * 2) for i in diameter_km]
    # Distance from the sun expressed in millions of km
    distance_from_sun = [0, 57.9, 108.2, 149.6, 227.9, 778.6, 1433.5, 2872.5, 4495.1]
    
    # Create spheres for the Sun and planets
    trace0=spheres(diameter[0], '#ffff00', distance_from_sun[0]) # Sun
    trace1=spheres(diameter[1], '#87877d', distance_from_sun[1]) # Mercury
    trace2=spheres(diameter[2], '#d23100', distance_from_sun[2]) # Venus
    trace3=spheres(diameter[3], '#325bff', distance_from_sun[3]) # Earth
    trace4=spheres(diameter[4], '#b20000', distance_from_sun[4]) # Mars
    trace5=spheres(diameter[5], '#ebebd2', distance_from_sun[5]) # Jupyter
    trace6=spheres(diameter[6], '#ebcd82', distance_from_sun[6]) # Saturn
    trace7=spheres(diameter[7], '#37ffda', distance_from_sun[7]) # Uranus
    trace8=spheres(diameter[8], '#2500ab', distance_from_sun[8]) # Neptune
    
    # Set up orbit traces
    trace11 = orbits(distance_from_sun[1]) # Mercury
    trace12 = orbits(distance_from_sun[2]) # Venus
    trace13 = orbits(distance_from_sun[3]) # Earth
    trace14 = orbits(distance_from_sun[4]) # Mars
    trace15 = orbits(distance_from_sun[5]) # Jupyter
    trace16 = orbits(distance_from_sun[6]) # Saturn
    trace17 = orbits(distance_from_sun[7]) # Uranus
    trace18 = orbits(distance_from_sun[8]) # Neptune
    
    # Use the same to draw a few rings for Saturn
    trace21 = orbits(23, distance_from_sun[6], '#827962', 3) 
    trace22 = orbits(24, distance_from_sun[6], '#827962', 3) 
    trace23 = orbits(25, distance_from_sun[6], '#827962', 3)
    trace24 = orbits(26, distance_from_sun[6], '#827962', 3) 
    trace25 = orbits(27, distance_from_sun[6], '#827962', 3) 
    trace26 = orbits(28, distance_from_sun[6], '#827962', 3)
    
    layout=go.Layout(title = 'Solar System', showlegend=False, margin=dict(l=0, r=0, t=0, b=0),
                  #paper_bgcolor = 'black',
                  scene = dict(xaxis=dict(title='Distance from the Sun', 
                                          titlefont_color='black', 
                                          range=[-7000,7000], 
                                          backgroundcolor='black',
                                          color='black',
                                          gridcolor='black'),
                               yaxis=dict(title='Distance from the Sun',
                                          titlefont_color='black',
                                          range=[-7000,7000],
                                          backgroundcolor='black',
                                          color='black',
                                          gridcolor='black'
                                          ),
                               zaxis=dict(title='', 
                                          range=[-7000,7000],
                                          backgroundcolor='black',
                                          color='white', 
                                          gridcolor='black'
                                         ),
                               annotations=[
                                   annot(distance_from_sun[0], 40, 'Sun', xancr='left'),
                                   annot(distance_from_sun[1], 5, 'Mercury'),
                                   annot(distance_from_sun[2], 9, 'Venus'),
                                   annot(distance_from_sun[3], 9, 'Earth'),
                                   annot(distance_from_sun[4], 7, 'Mars'),
                                   annot(distance_from_sun[5], 30, 'Jupyter'),
                                   annot(distance_from_sun[6], 28, 'Saturn'),
                                   annot(distance_from_sun[7], 20, 'Uranus'),
                                   annot(distance_from_sun[8], 20, 'Neptune'),
                                   ]
                               ))
    
    fig = go.Figure(data = [trace0, trace1, trace2, trace3, trace4, trace5, trace6, trace7, trace8,
                        trace11, trace12, trace13, trace14, trace15, trace16, trace17, trace18,
                        trace21, trace22, trace23, trace24, trace25, trace26],
                layout = layout)
    
    fig.show()
    fig.write_html("Solar_system.html")
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在开始项目之前,我们需要记录行星与其恒星之间的距离以及它们的大小。请注意,在绘制图表时如果不下降至适当规模,则绘制出的图形可能会超出预期的空间范围。因此,在数据可视化过程中保持比例一致性至关重要

虽然我们在行星大小的比例上有所保留以确保其相互间的正确比值但在太阳的情况中并不遵循这一原则。此外 行星之间的距离是以比例为基础进行计算的一种方法例如木星到太阳的距离大约是金星的七倍之多。然而 在这种情况下 我们并未维持行星间的相对比例以及与太阳的距离关系 因此 并未实现与其他天体间应有的距离协调

接下来我们需要调用预先定义好的函数来生成所有行星及其轨道的轨迹模型,并特别提到了土星环系统中的多个环结构。

在这里插入图片描述

最后,在布局设置中设定若干参数,在图中增加相关备注,并将所有追踪信息整合至go.Figure对象后生成图表

To display charts in a Jupyter Notebook, it is sufficient to run the fig.show() command. Additionally, the fig.write_html() method can be used to export an interactive HTML file.

项目文件

复制代码
    import numpy as np
    import math
    import plotly.graph_objects as go
    #from math import 
    
    def spheres(size, clr, dist=0): 
    
    # Set up 100 points. First, do angles
    theta = np.linspace(0,2*np.pi,100)
    phi = np.linspace(0,np.pi,100)
    
    # Set up coordinates for points on the sphere
    x0 = dist + size * np.outer(np.cos(theta),np.sin(phi))
    y0 = size * np.outer(np.sin(theta),np.sin(phi))
    z0 = size * np.outer(np.ones(100),np.cos(phi))
    
    # Set up trace
    trace= go.Surface(x=x0, y=y0, z=z0, colorscale=[[0,clr], [1,clr]])
    trace.update(showscale=False)
    
    return trace
    	
    	
    def orbits(dist, offset=0, clr='white', wdth=2): 
    
    # Initialize empty lists for eac set of coordinates
    xcrd=[]
    ycrd=[]
    zcrd=[]
    
    # Calculate coordinates
    for i in range(0,361):
        xcrd=xcrd+[(round(np.cos(math.radians(i)),5)) * dist + offset]
        ycrd=ycrd+[(round(np.sin(math.radians(i)),5)) * dist]
        zcrd=zcrd+[0]
    
    trace = go.Scatter3d(x=xcrd, y=ycrd, z=zcrd, marker=dict(size=0.1), line=dict(color=clr,width=wdth))
    return trace
    	
    def annot(xcrd, zcrd, txt, xancr='center'):
    strng=dict(showarrow=False, x=xcrd, y=0, z=zcrd, text=txt, xanchor=xancr, font=dict(color='white',size=12))
    return strng
    	
    
    # Note, true diameter of the Sun is 1,392,700km. Reduced it for better visualization
    diameter_km = [200000, 4878, 12104, 12756, 6787, 142796, 120660, 51118, 48600]
    diameter = [((i / 12756) * 2) for i in diameter_km]
    distance_from_sun = [0, 57.9, 108.2, 149.6, 227.9, 778.6, 1433.5, 2872.5, 4495.1]
    
    # Create spheres for the Sun and planets
    trace0=spheres(diameter[0], '#ffff00', distance_from_sun[0]) # Sun
    trace1=spheres(diameter[1], '#87877d', distance_from_sun[1]) # Mercury
    trace2=spheres(diameter[2], '#d23100', distance_from_sun[2]) # Venus
    trace3=spheres(diameter[3], '#325bff', distance_from_sun[3]) # Earth
    trace4=spheres(diameter[4], '#b20000', distance_from_sun[4]) # Mars
    trace5=spheres(diameter[5], '#ebebd2', distance_from_sun[5]) # Jupyter
    trace6=spheres(diameter[6], '#ebcd82', distance_from_sun[6]) # Saturn
    trace7=spheres(diameter[7], '#37ffda', distance_from_sun[7]) # Uranus
    trace8=spheres(diameter[8], '#2500ab', distance_from_sun[8]) # Neptune
    
    # Set up orbit traces
    trace11 = orbits(distance_from_sun[1]) # Mercury
    trace12 = orbits(distance_from_sun[2]) # Venus
    trace13 = orbits(distance_from_sun[3]) # Earth
    trace14 = orbits(distance_from_sun[4]) # Mars
    trace15 = orbits(distance_from_sun[5]) # Jupyter
    trace16 = orbits(distance_from_sun[6]) # Saturn
    trace17 = orbits(distance_from_sun[7]) # Uranus
    trace18 = orbits(distance_from_sun[8]) # Neptune
    
    # Use the same to draw a few rings for Saturn
    trace21 = orbits(23, distance_from_sun[6], '#827962', 3) 
    trace22 = orbits(24, distance_from_sun[6], '#827962', 3) 
    trace23 = orbits(25, distance_from_sun[6], '#827962', 3)
    trace24 = orbits(26, distance_from_sun[6], '#827962', 3) 
    trace25 = orbits(27, distance_from_sun[6], '#827962', 3) 
    trace26 = orbits(28, distance_from_sun[6], '#827962', 3)
    
    layout=go.Layout(title = 'Solar System', showlegend=False, margin=dict(l=0, r=0, t=0, b=0),
                  #paper_bgcolor = 'black',
                  scene = dict(xaxis=dict(title='Distance from the Sun', 
                                          titlefont_color='black', 
                                          range=[-7000,7000], 
                                          backgroundcolor='black',
                                          color='black',
                                          gridcolor='black'),
                               yaxis=dict(title='Distance from the Sun',
                                          titlefont_color='black',
                                          range=[-7000,7000],
                                          backgroundcolor='black',
                                          color='black',
                                          gridcolor='black'
                                          ),
                               zaxis=dict(title='', 
                                          range=[-7000,7000],
                                          backgroundcolor='black',
                                          color='white', 
                                          gridcolor='black'
                                         ),
                               annotations=[
                                   annot(distance_from_sun[0], 40, 'Sun', xancr='left'),
                                   annot(distance_from_sun[1], 5, 'Mercury'),
                                   annot(distance_from_sun[2], 9, 'Venus'),
                                   annot(distance_from_sun[3], 9, 'Earth'),
                                   annot(distance_from_sun[4], 7, 'Mars'),
                                   annot(distance_from_sun[5], 30, 'Jupyter'),
                                   annot(distance_from_sun[6], 28, 'Saturn'),
                                   annot(distance_from_sun[7], 20, 'Uranus'),
                                   annot(distance_from_sun[8], 20, 'Neptune'),
                                   ]
                               ))
    
    fig = go.Figure(data = [trace0, trace1, trace2, trace3, trace4, trace5, trace6, trace7, trace8,
                        trace11, trace12, trace13, trace14, trace15, trace16, trace17, trace18,
                        trace21, trace22, trace23, trace24, trace25, trace26],
                layout = layout)
    
    fig.show()
    fig.write_html("Solar_system.html")
    
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

全部评论 (0)

还没有任何评论哟~