.. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_viz_network_animated.py: ======================================================= Visualize Networks (Animated version) ======================================================= The goal of this demo is to show how to visualize a complex network and use an force directed algorithm to layout the network. A simpler animation of the network made by adding some random displacements to nodes positions is also demoed. First, let's import some useful functions .. code-block:: default import math from os.path import join as pjoin import numpy as np import vtk from vtk.util import numpy_support from fury import actor, window, colormap as cmap This demo has two modes. Use `mode = 0` to visualize a randomly generated geographic network by iterating it using a force-directed layout heuristic. Use `mode = 1` to visualize a large network being animated with random displacements .. code-block:: default mode = 0 Then let's download some available datasets. (mode 1) .. code-block:: default if(mode == 1): from fury.data.fetcher import fetch_viz_wiki_nw files, folder = fetch_viz_wiki_nw() categories_file, edges_file, positions_file = sorted(files.keys()) We read our datasets (mode 1) .. code-block:: default if(mode == 1): positions = np.loadtxt(pjoin(folder, positions_file)) categories = np.loadtxt(pjoin(folder, categories_file), dtype=str) edges = np.loadtxt(pjoin(folder, edges_file), dtype=np.int) vertices_count = len(positions) Generate a geographic random network, requires networkx package (mode 0) .. code-block:: default if(mode == 0): import networkx as nx vertices_count = 100 view_size = 100 network = nx.random_geometric_graph(vertices_count, 0.2) positions = view_size * \ np.random.random((vertices_count, 3)) - view_size / 2.0 categories = np.arange(0, vertices_count) edges = np.array(network.edges()) positions = view_size * \ np.random.random((vertices_count, 3)) - view_size / 2.0 We attribute a color to each category of our dataset which correspond to our nodes colors. .. code-block:: default category2index = {category: i for i, category in enumerate(np.unique(categories))} index2category = np.unique(categories) category_colors = cmap.distinguishable_colormap(nb_colors=len(index2category)) colors = np.array([category_colors[category2index[category]] for category in categories]) We define our node size .. code-block:: default radii = 1 + np.random.rand(len(positions)) Let's create our edges now. They will indicate a citation between two nodes. The colors of each edge are interpolated between the two endpoints. .. code-block:: default edges_colors = [] for source, target in edges: edges_colors.append(np.array([colors[source], colors[target]])) edges_colors = np.average(np.array(edges_colors), axis=1) Our data preparation is ready, it is time to visualize them all. We start to build 2 actors that we represent our data : sphere_actor for the nodes and lines_actor for the edges. .. code-block:: default sphere_actor = actor.sphere(centers=np.zeros(positions.shape), colors=colors, radii=radii * 0.5, theta=8, phi=8) lines_actor = actor.line(np.zeros((len(edges), 2, 3)), colors=edges_colors, lod=False, fake_tube=True, linewidth=3) Defining timer callback and layout iterator .. code-block:: default def new_layout_timer(showm, edges_list, vertices_count, max_iterations=1000, vertex_initial_positions=None): view_size = 500 viscosity = 0.10 alpha = 0.5 a = 0.0005 b = 1.0 deltaT = 1.0 sphere_geometry = np.array(numpy_support.vtk_to_numpy( sphere_actor.GetMapper().GetInput().GetPoints().GetData())) geometry_length = sphere_geometry.shape[0] / vertices_count if(vertex_initial_positions is not None): pos = np.array(vertex_initial_positions) else: pos = view_size * \ np.random.random((vertices_count, 3)) - view_size / 2.0 velocities = np.zeros((vertices_count, 3)) def iterate(iterationCount): nonlocal pos, velocities for _ in range(iterationCount): forces = np.zeros((vertices_count, 3)) # repulstive forces for vertex1 in range(vertices_count): for vertex2 in range(vertex1): x1, y1, z1 = pos[vertex1] x2, y2, z2 = pos[vertex2] distance = math.sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1)) + alpha rx = (x2 - x1) / distance ry = (y2 - y1) / distance rz = (z2 - z1) / distance Fx = -b * rx / distance / distance Fy = -b * ry / distance / distance Fz = -b * rz / distance / distance forces[vertex1] += np.array([Fx, Fy, Fz]) forces[vertex2] -= np.array([Fx, Fy, Fz]) # attractive forces for vFrom, vTo in edges_list: if(vFrom == vTo): continue x1, y1, z1 = pos[vFrom] x2, y2, z2 = pos[vTo] distance = math.sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1)) Rx = (x2 - x1) Ry = (y2 - y1) Rz = (z2 - z1) Fx = a * Rx * distance Fy = a * Ry * distance Fz = a * Rz * distance forces[vFrom] += np.array([Fx, Fy, Fz]) forces[vTo] -= np.array([Fx, Fy, Fz]) velocities += forces * deltaT velocities *= (1.0 - viscosity) pos += velocities * deltaT pos[:, 0] -= np.mean(pos[:, 0]) pos[:, 1] -= np.mean(pos[:, 1]) pos[:, 2] -= np.mean(pos[:, 2]) counter = 0 def _timer(_obj, _event): nonlocal counter, pos counter += 1 if(mode == 0): iterate(1) else: pos[:] += (np.random.random(pos.shape) - 0.5) * 1.5 spheres_positions = numpy_support.vtk_to_numpy( sphere_actor.GetMapper().GetInput().GetPoints().GetData()) spheres_positions[:] = sphere_geometry + \ np.repeat(pos, geometry_length, axis=0) edges_positions = numpy_support.vtk_to_numpy( lines_actor.GetMapper().GetInput().GetPoints().GetData()) edges_positions[::2] = pos[edges_list[:, 0]] edges_positions[1::2] = pos[edges_list[:, 1]] lines_actor.GetMapper().GetInput().GetPoints().GetData().Modified() lines_actor.GetMapper().GetInput().ComputeBounds() sphere_actor.GetMapper().GetInput().GetPoints().GetData().Modified() sphere_actor.GetMapper().GetInput().ComputeBounds() showm.scene.ResetCameraClippingRange() showm.render() if counter >= max_iterations: showm.exit() return _timer All actors need to be added in a scene, so we build one and add our lines_actor and sphere_actor. .. code-block:: default scene = window.Scene() camera = scene.camera() scene.add(lines_actor) scene.add(sphere_actor) The final step! Visualize the result of our creation! Also, we need to move the camera a little bit farther from the network. you can increase the parameter max_iteractions of the timer callback to let the animation run for more time. .. code-block:: default showm = window.ShowManager(scene, reset_camera=False, size=( 900, 768), order_transparent=True, multi_samples=8) showm.initialize() scene.set_camera(position=(0, 0, -300)) timer_callback = new_layout_timer( showm, edges, vertices_count, max_iterations=200, vertex_initial_positions=positions) # Run every 16 milliseconds showm.add_timer_callback(True, 16, timer_callback) showm.start() window.record(showm.scene, size=(900, 768), out_path="viz_animated_networks.png") .. image:: /auto_examples/images/sphx_glr_viz_network_animated_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 16.711 seconds) .. _sphx_glr_download_auto_examples_viz_network_animated.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download :download:`Download Python source code: viz_network_animated.py ` .. container:: sphx-glr-download :download:`Download Jupyter notebook: viz_network_animated.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_