-
Notifications
You must be signed in to change notification settings - Fork 8
Visualization
In some cases, pure textual simulation output does not satisfy the demands of the users. To clarify topologies and activities of a simulation, for presentations and easy understanding, a graphical output interface may be very helpful instead - or in addition to - the textual one. For these cases, Vis has been developed.
Vis is an optional application for Shawn and makes use of the Cairo graphics library to create graphical simulation output. It is easy to integrate it into new or existing Shawn simulation projects and was designed to be easily extendable to fit the users needs. In short, it creates a graphical representation of the simulation world and allows to save it to an image, or even a sequence of images to create an animation.
This section shows how to install Vis into Shawn by using CMake for configuration. For this, we assume the Vis folder to be placed inside the apps subfolder of shawn, as this is where all officially provided shawn applications should be placed. This could be for example shawn/src/apps using shawn/src/apps/vis as Vis directory.
If this is done, we use CMake to configure the buildfiles to include Vis into the project by the following steps:
- Choose 'configure' to start the configuration.
- Set CONFIGURE_APPS as well as OPT_ENABLE_CAIRO to ON, as Vis uses and needs the Cairo library.
- Choose 'configure' a third time.
- Set MODULE_APPS_VIS to ON and provide include and library paths for Cairo by filling in INCLUDE_PATH_CAIRO and LIB_PATH_CAIRO.
- Choose 'configure' a last time and start generating the buildfiles. Vis should now be part of your shawn build project and ready to be used by your simulation projects.
Some parts of Vis are using the boost-regex library, e.g. for creating edges and changing properties. To use these parts, some more steps are needed inside CMake:
- Set OPT_ENABLE_CGAL to ON, as boost configuration is part of the CGAL configuration, and choose 'configure'.
- Provide the boost include path to INCLUDE_PATH_BOOST as well as INCLUDE_PATH_CGAL and LIB_PATH_CGAL for CGAL's include and library paths.
- Choose configure again. Now, boost-regex should also be ready to be used by Vis.
One main component of Vis are drawable nodes. These are the graphical representations of the network processors (nodes) inside the Shawn simulation. By initializing Vis, drawable nodes are generated automatically and may be visualized in a default appearance without further configuration.
Anyway, the appearance may also be changed in size, border- and fill-color, border-width, transparency and form, to fit visualization needs. See the tutorials for examples, how to achieve that. Additionally, it is easy to create and add more (new) properties as needed, or to create even new drawable node classes. See section "Extending Vis" on more informations about that.
Drawable edges represent "edges" (aka communication connections between nodes) of a Shawn simulation. They visualize node interconnections or even complete communication pathways, giving a deeper insight to the simulation network structure. Unlike nodes, drawable edges are not generated automatically, but may be generated by a single call of the corresponding CreateEdges task in a shawn simulation configuration file.
Like drawable nodes, appearance may be configured in terms of color and line width, which is also shown in the tutorials.
Another drawable object is a graphics object. Graphics are simple 2D images, that may be loaded from a PNG image file to create for example a background image for the simulation visualization.
Like nodes, edges and graphics, groups are elements which can be added to Vis' element list. Groups are some kind of "light containers", used to group several elements for easier configuration. One may add groups called "nodes1" and "nodes2" and add drawable nodes to them. After that, it is possible to configure (e.g. set color property) all nodes in that group at once by configuring the group instead of the nodes.
It was a main concept to allow free configuring of nearly all elements of Vis which are important for the visualization, like nodes and edges. For that, so called property sets were developed. Property sets are - as the name implies - sets of properties that define and hold the configuration of a Vis element. Properties may be of different type, like integer, double or vector properties, and property sets define a named combination of such properties.
Elements of Vis may be configured by setting up properties in the configuration file that correspond (by name) to a property inside the property set of that element. E.g. for configuring the size of a drawable node, set up a double property task that is connected to the node and to the "size" property, which is part of the node property set. See tutorials for more details.
To define how the simulation is visualized (resolution, scale), and what part of it (position), the concept of a camera object is used inside Vis. This camera may be configured as easy as all other parts of Vis by configuring properties of it's property set, but also provides a task for simplified setup.
This section provides several tutorials on how to setup, use and extend the Shawn visualization system.
To use Vis, a world needs to be defined first. See basic Shawn documentation and tutorials on how to do this. For simplicity, a simple world with 800 randomly placed processors is used here: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 After the simulation world is defined, the visualization engine can be initialized by the Vis creation task as shown below. Note that naming the visualization is optional, as it is named "visualization" by default: vis=my_visualization #optional vis_create Now, Vis is initialized but doesn't do anything yet. To generate an output, two more steps need to be done:
- Initialize the camera to define resolution, angle, world-scaling and other factors
- Run the visualization output task to create the output file(s) An example for this would be to simply call: vis_simple_camera vis_single_snapshot This creates a pdf document file named snapshot.pdf which contains an image of the 800 processor nodes defined above. The vis_simple_camera task eases - as the name implies - the camera setup by initializing it with some standard values, that are sufficient here to create an output. The vis_single_snapshot task then draws the simulation world and saves it to the output file.
These are really just the basic steps to create an output. See later tutorials for more details on how to configure and extend Vis and it's components for your own needs. The complete configuration for this tutorial looks like this: # Define simulation world: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 # Initialize Vis: vis_create # Camera setup (simple setup): vis_simple_camera # Create output image: vis_single_snapshot
To visualize communication connections of the network nodes, Vis can be configured to draw edges between them. To achieve this, the vis_create_edges task has to be run: vis_create_edges This visualizes all connections between all nodes of the simulation. It's also possible to define which edges to draw by providing regular expressions. vis_create_edges source_regex=.* target_regex=.* As shown above, the task supports two parameters, where source_regex defines which nodes to use as connection sources, and target_regex defines which nodes should be considered as possible connection targets. The sample used here simply creates edges for all connections between arbitrary nodes, and is therefore equivalent to the simple version above. Note, that boost has to be activated in order to support regular expressions. Without boost, the regex parameters are ignored.
The edge creation task has to be run after the initialization of Vis, but prior to creating the output image(s), resulting in a configuration like this: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 vis=my_visualization vis_create
# Create edges:
vis_create_edges source_regex=.* target_regex=.*
vis_simple_camera
vis_single_snapshot
It's possible to create edges based on special tags called Node Reference Tags that are attached to nodes. So your algorithm may add one of these tags for every node, the current node should be connected to. After that, a call of vis_create_dynamic_edges_bytags uses these tags to draw edges accordingly, as shown below:
# Create Edges using Tags
vis_create_dynamic_edges_bytags tagname_regex=mytags.*
To define which Node Reference Tags should be used for creating edges, the tagname_regex parameter is required. This way, the user is also free in choosing a base name convention for these tags. One example of using this feature would be to use tag names like "edgeto.<target_node_id>". After tags are created, just call
vis_create_dynamic_edges_bytags tagname_regex=edgeto.*
to create the edges.
Note: This Feature needs Boost Regex installed and enabled inside CMake.
In Vis, Nodes can be visually labeled, e.g. to identify them inside the output file or live visualization (Liveview). Currently, there are two versions of labels available in Vis, one for just showing the label (aka name) of a Shawn node, and one for showing the contents of a string tag which is attached to that node.
For just showing the node's label, add this line to your configuration file: vis_create_label
For creating labels which show the content of string tags attached to Shawn nodes, add this line instead of the above one: vis_create_label tag= stands for the name of the string tag you want to show. So if your tag is called "MyTag", just change the line accordingly: vis_create_label tag=MyTag It automatically uses the current content of the defined tag at the time the visualization is drawn, so no need to call the method again at later times to update the labels.
Note: Currently, the first label version is used as a "backup" for the second one. This means that, if the defined tag is not existing and/or it has a wrong type (not a string tag), the label (aka name) of the Shawn node is used instead. This may be changed in upcoming versions, but if Vis just shows the node's name instead of a tag content, check if the tag really exists on your nodes, and if it has the correct type.
See also the example configurations "labels and comradius.conf" and "taglabels.conf" under shawn/src/apps/vis/examples.
Currently, Vis officially supports three types of output files: PDF documents (default), PS documents and PNG image files. To change the writer type, just add the optional 'writer' attribute to the snapshot task: vis_single_snapshot writer=pdf # Writer attribute redundant, as pdf is default vis_single_snapshot writer=png # Use the PNG writer vis_single_snapshot writer=ps # Use the PS writer It is also possible to use different writers in one configuration. For that, just call them one after the other.
The following configuration, basing on the previous tutorial, creates a PNG image as well as a PDF document: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 vis=my_visualization vis_create vis_create_edges source_regex=.* target_regex=.*
vis_simple_camera
# PDF output:
vis_single_snapshot
# PNG output:
vis_single_snapshot writer=png
Vis supports an animation output window called "Liveview". In differance to the file writer types described above, using Liveview the network state is visualized automatically during a simulation and can be examined while the simulation runs. To enable this animation system, only a call of vis_create_liveview is needed as shown below. As Liveview uses a platform independant GLUT window, the GLUT library has to be available, and enabled using the CMAKE Option "OPT_ENABLE_GLUT". Additionally, the BOOST library has to be available and enabled as well (OPT_ENABLE_BOOST), being internally used for threading and timing. # Enable animation: vis_create_liveview This creates an external window and registers an update event to Shawn's event scheduler, which is called once in a simulation round by default, thus updating the visible image in this interval. One may change the interval by using the optional "refresh_interval" parameter. # Enable an animation refreshing twice a simulation round: vis_enable_liveview refresh_interval=0.5 The parameters value stands for a delay in Shawn simulation time, so 0.5 means to update twice a simulation round (at time .0 and .5), a value of 2.0 means to update every second simulation round, and so on. As this is all based on simulation time, the speed of refreshes in real time depends on the speed of the simulation by default. In some cases, this may be to fast to examine the running simulation. For theses cases, a second optional parameter is available to slow the simulation down automatically. # Enable an animation refreshing twice a simulation round with a minimum real time delay of 500ms: vis_create_liveview refresh_interval=0.5 refresh_delay=500 Inspite of using simulation time, as the "refresh_interval" parameter does, the "refresh_delay" parameter takes a real time value (in ms) which controls the minimal delay (in real time) between two refreshes. Using a value of 500, as shown in the example, configures Liveview to maximally update it's output twice a second. It may be less, if the simulation needs more than 500ms from one refresh to the next one. To achieve this minimal real-time delay, the simulation gets paused if needed. Some (ideal) examples, using the statement above to create Liveview:
Simulation round 1 needs 2 Seconds in real time to compute. As Liveview is configured to update twice a simulation round, the output is refreshed once a second, which is more than the 500ms chosen as minimum delay. Simulation round 2 needs half a second in real time to compute. Updating twice a round, the real-time delay would be 250ms between two refreshes. To assure the minimum delay of 500ms, the simulation gets paused 250ms every refresh.
The simpliest example configuration to use Liveview looks like this: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 vis=my_visualization vis_create vis_simple_camera
# Create Liveview
vis_create_liveview
simulation max_iterations=100
As the network keeps static, there is no animation as well, but only a window containing a static image. A more advanced example, showing some animation, is shown below. Note, that this configuration needs the "Examples" app to be enabled, as vis_energy processors are derived from helloworld_processor. prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=10 height=10 count=50 processors=vis_energy vis=my_visualization vis_create
# Camera setup:
vis_constant_vec x=5 y=5 z=0 elem_regex=cam prop=position prio=1
vis_constant_vec x=0 y=0 z=0 elem_regex=cam prop=position_shift prio=1
vis_constant_vec x=1 y=1 z=1 elem_regex=cam prop=background prio=1
vis_constant_double value=500 elem_regex=cam prop=width prio=1
vis_constant_double value=500 elem_regex=cam prop=height prio=1
vis_constant_double value=48 elem_regex=cam prop=scale prio=1
# Set color based on a tag (changing color, if the tag value changes):
vis_tag_color_vec elem_regex=node.* dynamictag=VisBattery prop=background prio=2
# Enable an animation refreshing twice a simulation round with a minimum real time delay of 500ms:
vis_create_liveview refresh_interval=0.5 refresh_delay=500
# Start simulation (and therefore liveview animation):
simulation max_iterations=100
One key aspect of Vis is the usage of so called properties. There are properties of different types available, like double, integer or 3D vector properties. Other elements of Vis, like the camera or drawable node and edge objects, use their own property sets to define which properties they provide and of what type they are. These properties can be configured by the user by calling for example vis_constant_vec x=1 y=1 z=0 elem_regex=node.default.* prop=background prio=1 This example sets the color of all nodes to yellow. In brief, the attributes of a property define the properties value(s) (x,y,z here as the three components of a 3D vector property), the elements the property shall be attached to (elem_regex, a regular expression), the name of the property in the elements property set (background means the nodes fill color in this example) and the properties priority.
By using properties, one may easily configure the visualization directly inside the configuration file without having to recompile the whole application. Additionally, the property system was written to be easily extensible for the users special needs.
Other property types can be configured in a similar way. For example, the following call sets the size of all drawable nodes to a double value of 0.1. The second call sets it to 0.2, but only for nodes with a name beginning with "node.default.v1" (which means, that the corresponding processor's ID starts with v1): vis_constant_double value=0.1 elem_regex=node.default.* prop=size prio=1 vis_constant_double value=0.2 elem_regex=node.default.v1.* prop=size prio=2 Please note that the priority attribute of the second call is set to '2'. This is mandatory in this example, as the first call already sets the size for 'all' nodes. Using a higher priority in the following call defines to use the new value instead the old one set by the first call. You may also swap the two lines, which has the same effect in this example.
A node's primary properties in Vis are - besides it's position - color, size and shape, where color is separated into foreground (border) and background (fill) color. To setup foreground and background colors, call the following property tasks: #Sets a dark blue border color: vis_constant_vec x=0 y=0 z=0.7 elem_regex=node.default.* prop=foreground prio=1 #Sets a yellow fill color: vis_constant_vec x=1 y=1 z=0 elem_regex=node.default.* prop=background prio=1 Note, that colors in Vis are set as floating point RGB color channels, with the "x" component of the color vector standing for the red, "y" for the green and "z" for the blue color channel and values ranging from zero to one. Default colors are (0,0,0) = black as border color and (0.7,0.7,0) = some yellowish as fill color.
To setup the size of nodes, use the double-typed size property: vis_constant_double value=0.1 elem_regex=node.default.* prop=size prio=1 This simply sets the size to the value of 0.1, which is a little smaller than the default size. Note, that a value of 0.1 does not stand for a size that is 10% of the default size, as default size is 0.15 (so it's 2/3 of the default size actually).
The shape of a node is an integer value, therefore an integer property needs to be created: vis_constant_int value=1 elem_regex=node.default.* prop=shape prio=1 There are two shapes available currently, a circle (value=1) and a quad (value=2), with the circle being the default shape.
Additionally, a node has a "blend" property. This property simply blends the the node to the background color of the camera (see camera setup tutorials) to blend it in or out (e.g. for animation purposes). vis_constant_double value=0.5 elem_regex=node.default.* prop=blend prio=1 Default blend value is 0.0, which means not to blend it at all. Highest value accepted is 1.0, resulting in a complete blend-out.
Edges properties are it's line-width and color. As nodes, edges support blending to the background color of the camera with the blend property. The setup of edge properties is very similar to set up nodes properties. #Sets a black edge color: vis_constant_vec x=0 y=0 z=0 elem_regex=edge.default.* prop=color prio=1 Like all color values in Vis, this vector property holds floating point RGB data ranging from zero to one. The default edge color is red (1,0,0). As for nodes, you can define which edges will be affected by the property with help of the elem_regex attribute. The example above simply sets the color for all edges. Edges don't have a border, so the property is just named "color", as no distinction of foreground and background color is needed.
Line-with is a double-typed property which can be configured in the following way: vis_constant_double value=0.05 elem_regex=edge.default.* prop=line_width prio=1 Like configuring the size of nodes, a value of 0.05 does not mean 5% of the default line_width, as the default line_width is 0.002, a value of 0.05 leads to a much thicker edge line.
The blend property is configured exactly the same way as doing it for nodes, with just changing the elem_regex: vis_constant_double value=0.5 elem_regex=edge.default.* prop=blend prio=1
To summarize the configuration of nodes and edges, a configuration is given, that shows the influence of different values to different elements of the visualized simulation: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 vis=my_visualization vis_create vis_create_edges source_regex=.* target_regex=.*
# Configuring node properties:
# Node colors (Yellow for all processors with ID v1.*, blue for ID v2.*, default for all others):
vis_constant_vec x=1 y=1 z=0 elem_regex=node.default.v1.* prop=background prio=1
vis_constant_vec x=1 y=0 z=0 elem_regex=node.default.v2.* prop=background prio=1
# Node size:
vis_constant_double value=0.2 elem_regex=node.default.* prop=size prio=1
vis_constant_double value=0.25 elem_regex=node.default.v1.* prop=size prio=1
vis_constant_double value=0.3 elem_regex=node.default.v2.* prop=size prio=1
# Node shape (quadrat):
vis_constant_int value=2 elem_regex=node.default.v1.* prop=shape prio=1
# Configure edge properties:
# Line width:
vis_constant_double value=0.07 elem_regex=edge.default.* prop=line_width prio=1
# Line color 1 (for all lines starting from processors with ID v1.*):
vis_constant_vec x=0 y=0 z=0.8 elem_regex=edge.default.v1.* prop=color prio=1
# Line color 2 (for all lines starting from processors with ID v2.*):
vis_constant_vec x=0 y=0.8 z=0 elem_regex=edge.default.v1.* prop=color prio=1
vis_simple_camera
vis_single_snapshot
In the examples above, vis_simple_camera was used to setup the camera. This section shows, which properties need to be set to create a usable camera and how to set them up manually.
One of the easiest things is to change the image resolution. For that, the width and height properties of the camera have to be set to the preferred resolution before calling vis_simple_camera: vis_constant_double value=1280 elem=cam prio=0 prop=width vis_constant_double value=800 elem=cam prio=0 prop=height vis_simple_camera The example above sets the camera (and therefore output) resolution to 1280x800 pixels, and after that, vis_simple_camera automatically adjusts all other camera properties like world scaling.
To configure the camera manually without any usage of vis_simple_camera, all properties have to be set by hand:
- 'position' is the position of the camera in 3D space, and therefore a vector property. To be centered, the horizontal position should be half of the world width, and die vertical position half of the world height. Note that the camera position is set in world coordinates, not in pixel coordinates!
- 'position_shift' allows to move the camera horizontally and/or vertically by a translation vector. To stay centered, just use a zero vector. The position shift is set in pixel values, therefore a x-value of 100 would shift the camera 100 pixel to the left.
- 'background' denotes the background color. It is set as a RGB color vector with values between 0 and 1 as usual.
- 'width' defines the horizontal resolution of the output image.
- 'height' defines the vertical resolution of the output image.
- 'scale' defines the factor, the world size is scaled with. Having for example a world of size 25x25 units and a resolution of 1000x1000 pixels, a scaling factor of 40 (1000/25) may be used to scale the world to the image resolution. Actually, the scaling factor should be a little smaller for nodes at the world borders not being truncated, so 38 would be a good choice in this example.
The following setup would set up the camera manually, assuming a world size of 25x25 units, with a white-colored background, a world-centered camera and a resolution of 1000x1000 pixels: vis_constant_vec x=12.5 y=12.5 z=0 elem_regex=cam prop=position prio=1 vis_constant_vec x=0 y=0 z=0 elem_regex=cam prop=position_shift prio=1 vis_constant_vec x=1 y=1 z=1 elem_regex=cam prop=background prio=1 vis_constant_double value=1000 elem_regex=cam prop=width prio=1 vis_constant_double value=1000 elem_regex=cam prop=height prio=1 vis_constant_double value=38 elem_regex=cam prop=scale prio=1
This way, it is also possible to just visualize a part of the simulation, e.g. for getting a better overview and more details of this part. For this, just change camera position and world scaling as needed. The following setup only draws the upper right quarter of the simulation world: vis_constant_vec x=18.75 y=18.75 z=0 elem_regex=cam prop=position prio=1 vis_constant_vec x=0 y=0 z=0 elem_regex=cam prop=position_shift prio=1 vis_constant_vec x=1 y=1 z=1 elem_regex=cam prop=background prio=1 vis_constant_double value=1000 elem_regex=cam prop=width prio=1 vis_constant_double value=1000 elem_regex=cam prop=height prio=1 vis_constant_double value=76 elem_regex=cam prop=scale prio=1
Based on the configuration of the previous tutorials, the following one is extended by manual camera setup and shows the upper right quarter of the simulation: prepare_world edge_model=simple comm_model=disk_graph range=1.5 rect_world width=25 height=25 count=800 vis=my_visualization vis_create vis_create_edges source_regex=.* target_regex=.* vis_constant_vec x=1 y=1 z=0 elem_regex=node.default.v1.* prop=background prio=1 vis_constant_vec x=1 y=0 z=0 elem_regex=node.default.v2.* prop=background prio=1 vis_constant_double value=0.2 elem_regex=node.default.* prop=size prio=1 vis_constant_double value=0.25 elem_regex=node.default.v1.* prop=size prio=1 vis_constant_double value=0.3 elem_regex=node.default.v2.* prop=size prio=1 vis_constant_int value=2 elem_regex=node.default.v1.* prop=shape prio=1 vis_constant_double value=0.07 elem_regex=edge.default.* prop=line_width prio=1 vis_constant_vec x=0 y=0 z=0.8 elem_regex=edge.default.v1.* prop=color prio=1 vis_constant_vec x=0 y=0.8 z=0 elem_regex=edge.default.v1.* prop=color prio=1
## Camera setup:
# Camera position (world coordinates):
vis_constant_vec x=18.75 y=18.75 z=0 elem_regex=cam prop=position prio=1
# Position shift:
vis_constant_vec x=0 y=0 z=0 elem_regex=cam prop=position_shift prio=1
# Background color (white):
vis_constant_vec x=1 y=1 z=1 elem_regex=cam prop=background prio=1
# Resolution (width and height):
vis_constant_double value=500 elem_regex=cam prop=width prio=1
vis_constant_double value=500 elem_regex=cam prop=height prio=1
# World scaling:
vis_constant_double value=38 elem_regex=cam prop=scale prio=1
vis_single_snapshot
It is possible to add images (graphics) to the visualization.
The easiest way of adding an image is the static case. It simply adds an image to the visualization, and offers the following properties:
- 'position' is the position, where the image is placed. It expects a vector, and thus the parameters x, y, and z.
- 'scale' the scale factor as a double value that is used for drawing the image. Setting it to 2, for example, would draw the image in twice the size.
- 'blend' defines a value for the transparency, as a double value between 0 and 1 (1 = image is fully transparent; 0 = no transparency).
The following setup would add a background image that is nearly transparent: vis_graphics name=bg_graphic file=backgroud.png vis_constant_vec x=0 y=0 z=0 elem=bg_graphic prop=position prio=5 vis_constant_double value=1.3 elem=bg_graphic prop=scale prio=1 vis_constant_double value=0.8 elem=bg_graphic prop=blend prio=1
It is also possible to load images for nodes. That can either be done statically (via filename in configuration file), or dynamically (using tags). The drawable node image offers the following properties:
- 'size' the scale factor as a double value that is used for drawing the image. Setting it to 2, for example, would draw the image in twice the size.
- 'blend' defines a value for the transparency, as a double value between 0 and 1 (1 = image is fully transparent; 0 = no transparency).
The functionality can be used by setting the parameter node_type to graphics, when calling vis_create. The default prefix for nodes drawn that way is node.graphics.. An example of representing all nodes with the image my_image.png looks as follows: vis_create node_type=graphics filename=my_image.png vis_constant_double value=0.3 elem_regex=node.graphics.* prop=size prio=1
When images shall be loaded 'dynamically', the string tag node_graphics_fname must set to the corresponding path to the image.
This tutorial shows, how Vis has been extended to visualize the estimated position of a node. The estimated position of a node is - in general - the position which is computed from only the informations the node gets, and may actually differ very much from it's real position inside of the simulation world. To visualize this difference, additional edges are created from a nodes real position to it's estimated position.
Unfortunately, the default edges (of type DrawableEdgeDefault) don't support drawing an edge from a real to the estimated position, but only from one (real) node position to another one's. Therefore, the first step is to create a new subtype of DrawableEdges, which is called the DrawableEdgeEstimated type (comparable to DrawableEdgeDefault which is used for normal edges).
For this, two new files are created inside the "vis/elements" folder, vis_drawable_edge_estimated.h and vis_drawable_edge_estimated.cpp. To fill the header file, we need to set and test the defines as usual: #ifndef __VIS_DRAWABLE_EDGE_ESTIMATED_H #define __VIS_DRAWABLE_EDGE_ESTIMATED_H #include "../buildfiles/_legacyapps_enable_cmake.h" #ifdef ENABLE_VIS
Some other includes are needed as well. As this class will be a Drawable edge, we need the DrawableEdge include, and because an edge saves it's corresponding nodes (and uses it's properties e.g. for positioning), the DrawableNode include is needed as well. The third include gives access the an edge's property set and the last one is for cairo, which is used for drawing the edge: #include "legacyapps/vis/elements/vis_drawable_edge.h" #include "legacyapps/vis/elements/vis_drawable_node.h" #include "legacyapps/vis/properties/vis_edge_property_set.h" #include "legacyapps/vis/base/vis_needs_cairo.h"
As everything in Vis, this class will be added to the vis namespace: namespace vis { class DrawableEdgeEstimated : public DrawableEdge { public: static const std::string PREFIX;
DrawableEdgeEstimated( const shawn::Node&,
const DrawableNode&,
const std::string& p = PREFIX);
virtual ~DrawableEdgeEstimated();
virtual void init( void ) throw();
virtual void draw( cairo_t*, double, const Context& ) const throw(std::runtime_error);
virtual const PropertySet& properties( void ) const throw();
virtual PropertySet& properties_w( void ) throw();
inline EdgePropertySet& edge_properties_w( void ) throw()
{ assert( props_.is_not_null() ); return *props_; }
inline const EdgePropertySet& edge_properties( void ) const throw()
{ assert( props_.is_not_null() ); return *props_; }
private:
shawn::RefcntPointer<EdgePropertySet> props_;
const DrawableNode* src_drawable_;
};
}
#endif
#endif
As you may see, the class looks pretty much the same as the DrawableEdgeDefault class, with only difference in constructor parameters. We only need one node per edge here and therefore only one DrawableNode as well. So let's implement these methods in the .cpp file: #include "../buildfiles/_legacyapps_enable_cmake.h" #ifdef ENABLE_VIS
#include "legacyapps/vis/elements/vis_drawable_edge_estimated.h"
namespace vis
{
const std::string DrawableEdgeEstimated::PREFIX("estimated");
The first difference concerns the PREFIX variable, which is used to build up the name of an edge. Default edges are using the "default" prefix, so our EstimatedEdge uses "estimated" instead.
DrawableEdgeEstimated::
DrawableEdgeEstimated( const shawn::Node& v1,
const DrawableNode& dv1,
const std::string& p )
: DrawableEdge( std::string("edge.") + p, v1, v1 ),
props_ ( NULL ),
src_drawable_ ( &dv1 )
{}
DrawableEdgeEstimated::
~DrawableEdgeEstimated()
{}
The constructor just initializes some values. DrawableEdges normally need two nodes, so we just use the one node we have twice to initialize the edge. An empty destructor is provided as usual.
void
DrawableEdgeEstimated::
init( void )
throw()
{
props_ = new EdgePropertySet;
props_->init(this);
DrawableEdge::init();
}
The init method initializes the edge object by creating it's property set and calling the base init method. Now, the draw-method is provided, using cairo to draw the edge onto the provided cairo surface:
void
DrawableEdgeEstimated::
draw( cairo_t cr, double t, const Context& C )
const throw(std::runtime_error)
{
Drawable::draw(cr,t,C);
if( visible() )
{
shawn::Vec pos1 = src_drawable_->position(t);
const shawn::Vec pos2 = src_drawable_->node().est_position();
double lw = edge_properties().line_width(t);
shawn::Vec col = edge_properties().color(t);
double blend = edge_properties().blend(t);
cairo_save(cr);
cairo_set_line_width( cr, lw );
cairo_set_source_rgba(cr,col.x(),col.y(),col.z(),1.0-blend);
//blend_set_color(cr,col);
cairo_move_to(cr,pos1.x(),pos1.y());
cairo_line_to(cr,pos2.x(),pos2.y());
cairo_stroke(cr);
cairo_restore(cr);
}
}
Unlike to the default edge, we use the nodes estimated position as the edges target (pos2), instead of a second's node position. This is pretty much all we have to change regarding the default node. The last part are the getter methods for the property set: const PropertySet& DrawableEdgeEstimated:: properties( void ) const throw() { assert( props_.is_not_null() ); return *props_; } PropertySet& DrawableEdgeEstimated:: properties_w( void ) throw() { assert( props_.is_not_null() ); return *props_; } }
#endif
After the new edge type has been created, we need a task to create these edges, similar to what is done inside of the create_edges_task which creates the default edges. For this, create two new files named vis_create_estimated_edges.h and vis_create_estimated_edges.cpp and place them into the "vis/tasks" folder. Comparable to the edge-class, the create_estimated_edges_task looks pretty much the same as the create_edges_task. Besides the class name, nothing is changed in the header file: #ifndef __VIS_CREATE_ESTIMATED_EDGES_H #define __VIS_CREATE_ESTIMATED_EDGES_H #include "../buildfiles/_legacyapps_enable_cmake.h" #ifdef ENABLE_VIS
#include "legacyapps/vis/base/vis_task.h"
#include "legacyapps/vis/elements/vis_group_element.h"
#include "legacyapps/vis/elements/vis_drawable_node.h"
namespace vis
{
class CreateEstimatedEdgesTask
: public VisualizationTask
{
public:
CreateEstimatedEdgesTask();
virtual ~CreateEstimatedEdgesTask();
virtual std::string name( void ) const throw();
virtual std::string description( void ) const throw();
virtual void run( shawn::SimulationController& sc )
throw( std::runtime_error );
protected:
virtual GroupElement* group( shawn::SimulationController& sc )
throw( std::runtime_error );
virtual const DrawableNode* drawable_node( const shawn::Node&,
const std::string& nprefix )
throw( std::runtime_error );
};
}
#endif
#endif
Inside of the .cpp-file, there are some more changes needed. We need an include of the new estimated edge class, while boost-regex support isn't needed in this task: #include "../buildfiles/_legacyapps_enable_cmake.h" #ifdef ENABLE_VIS #include "legacyapps/vis/tasks/vis_create_estimated_edges.h" #include "legacyapps/vis/elements/vis_drawable_edge_estimated.h" #include "legacyapps/vis/elements/vis_drawable_node_default.h"
using namespace shawn;
namespace vis
{
CreateEstimatedEdgesTask::
CreateEstimatedEdgesTask()
{}
CreateEstimatedEdgesTask::
~CreateEstimatedEdgesTask()
{}
We have to provide a name and a description for our new task: std::string CreateEstimatedEdgesTask:: name( void ) const throw() { return "vis_create_estimated_edges"; } std::string CreateEstimatedEdgesTask:: description( void ) const throw() { return "Creates edges from real to estimated node positions."; } Now lets create our estimated edges inside the run method. Define, which node and edge prefix to use: void CreateEstimatedEdgesTask:: run( shawn::SimulationController& sc ) throw( std::runtime_error ) { VisualizationTask::run(sc);
std::string pref = sc.environment().
optional_string_param("prefix",DrawableEdgeEstimated::PREFIX);
std::string node_prefix =
sc.environment().
optional_string_param("node_prefix",DrawableNodeDefault::PREFIX);
Try to catch up a group to attach our edges to: GroupElement* grp = group(sc); Then iterate through all nodes: for( shawn::World::const_node_iterator it = visualization().world().begin_nodes(), endit = visualization().world().end_nodes(); it != endit; ++it ) { We only have to draw something, if the node provides an estimated position: if( it->has_est_position()) { Get the corresponding DrawableNode object and create our estimated edge: const DrawableNode* dn = drawable_node(it, node_prefix); DrawableEdgeEstimated dee = new DrawableEdgeEstimated(it, dn, DrawableEdgeEstimated::PREFIX); Initialize it and add it to our visualization as well as to the group, if provided: dee->init(); visualization_w().add_element(dee); if( grp != NULL ) grp->add_element(dee); } } } We used two helper methods, group and drawable_node, so we should implement them: GroupElement CreateEstimatedEdgesTask:: group( shawn::SimulationController& sc ) throw( std::runtime_error ) { std::string n = sc.environment().optional_string_param("group",""); if( n.empty() ) return NULL; ElementHandle eh = visualization_w().element_w( n ); if( eh.is_null() ) throw std::runtime_error(std::string("no such group: ")+n); GroupElement ge = dynamic_cast<GroupElement>(eh.get()); if( ge == NULL ) throw std::runtime_error(std::string("element is no group: ")+n); return ge; }
const DrawableNode*
CreateEstimatedEdgesTask::
drawable_node( const shawn::Node& v,
const std::string& nprefix )
throw( std::runtime_error )
{
std::string n = nprefix+std::string(".")+v.label();
ConstElementHandle eh =
visualization().element( n );
if( eh.is_null() )
throw std::runtime_error(std::string("no such element: ")+n);
const DrawableNode* dn = dynamic_cast<const DrawableNode*>(eh.get());
if( dn == NULL )
throw std::runtime_error(std::string("element is no DrawableNode: ")+n);
return dn;
}
}
#endif
Now we have a new edge type and a task to create this edges. The last step is to just register the task with the simulation task keeper. This is done inside the vis/tasks/vis_tasks_init.cpp file. First, add the header file include of the new task: [...] #include "legacyapps/vis/tasks/vis_create_edges.h" #include "legacyapps/vis/tasks/vis_create_estimated_edges.h" #include "legacyapps/vis/tasks/vis_create_graphics.h" [...] Now we can register the new task inside the init_vis_tasks method: [...] sc.simulation_task_keeper_w().add( new CreateEdgesTask ); sc.simulation_task_keeper_w().add( new CreateEstimatedEdgesTask ); sc.simulation_task_keeper_w().add( new CreateGraphicsTask ); [...]
To try out the new task, activate the Localization App, which is part of the Shawn Apps (activate CONFIGURE_APPS in CMake and set MODULE_APPS_LOCALIZATION to ON). The following configuration file draws all nodes as well as the edges to their estimated positions, which should look similar to the picture shown at the beginning of this section. random_seed action=create filename=.rseed
prepare_world edge_model=list comm_model=disk_graph transm_model=stats_chain range=3
chain_transm_model name=reliable
loc_est_dist=perfect_estimate
loc_dist_algo=dv_hop
loc_pos_algo=min_max
loc_ref_algo=none
rect_world width=25 height=25 count=225 processors=localization
localization_anchor_placement anchor_placement=outer_grid placed_anchor_cnt=9
vis=my_visualization
vis_create
simulation max_iterations=50
# Create estimated edges:
vis_create_estimated_edges
# Change properties of the estimated edges:
vis_constant_vec x=.6 y=.6 z=.6 elem_regex=edge.estimated.* prop=color prio=5
vis_constant_double value=0.07 elem_regex=edge.* prop=line_width prio=1
# Resolution (width and height):
vis_constant_double value=500 elem_regex=cam prop=width prio=1
vis_constant_double value=500 elem_regex=cam prop=height prio=1
vis_simple_camera
vis_single_snapshot filename=estimatedEdges
It is already possible to use tags to configure a node's properties by using the tag param as shown above. But one might need a more flexible way to always draw a node in dependence to the current value of a given tag. This tutorial shows how to create a new property to perform this task. In brief, the new property reads a double tag (for example a nodes energy level) and generates a color between red and blue based on the tag's value.
To create a new property, we need to define the property class itself, derived from the property base template Property<>, as well as a corresponding property task, derived from the PropertyTask class. In this case, we call the property class 'PropertyTagColorVec' and the corresponding task 'PropertyTagColorVecTask'. This leads us to the skeletal structure of our 'vis_property_tag_color_vec.h':
#ifndef __VIS_TAG_COLOR_VEC_H
#define __VIS_TAG_COLOR_VEC_H
#include "../buildfiles/_legacyapps_enable_cmake.h"
#ifdef ENABLE_VIS
#include "legacyapps/vis/properties/vis_property_task.h"
#include "legacyapps/vis/properties/vis_property.h"
#include "legacyapps/vis/properties/vis_property_stack.h"
#include <string>
namespace shawn
{ class Node; }
namespace vis
{
class PropertyTagColorVecTask
: public PropertyTask
{
public:
class PropertyTagColorVec
: public Property<shawn::Vec>
{
// Property member
};
// Task member
};
}
#endif
#endif
As you may see, the new property is derived from the property template using shawn::Vec as property type. At first, we will implement this property. For basic functionality, we need the constructor, destructor and a 'value' method to retrieve the current value of the property:
public:
PropertyTagColorVec( const shawn::Node*, std::string tag );
virtual ~PropertyTagColorVec();
virtual shawn::Vec value( double, const PropertyStack<shawn::Vec>&,
const Element& ) const throw();
To create an instance of our new property, we need to know the node we want to read the tag from, as well as the name of this tag, so the constructor takes the two as arguments. The 'value' method just implements the abstract definition of the 'Property' template. Additionally, we keep the node and tag name as member variables and define a helper method to read the tag's value:
private:
const shawn::Node* node_;
std::string tag_;
double read_tag(const Element& e) const throw();
This is all we have to define in the property class. Let's implement this methods inside the 'vis_property_tag_color_vec.cpp' file. The constructor just initializes the member variables as shown below: #include "../buildfiles/_legacyapps_enable_cmake.h" #ifdef ENABLE_VIS #include "legacyapps/vis/properties/vec/vis_property_tag_color_vec.h" #include "legacyapps/vis/elements/vis_drawable_node.h" #include "sys/taggings/basic_tags.h"
using namespace shawn;
namespace vis
{
PropertyTagColorVecTask::PropertyTagColorVec::
PropertyTagColorVec( const shawn::Node* n, std::string dynamictag )
: node_(n),
tag_(dynamictag)
{}
PropertyTagColorVecTask::PropertyTagColorVec::
~PropertyTagColorVec()
{}
// following methods...
#endif
The value method is used to read the properties current value, e.g. when a node is drawn. We read out the tag's value and generate a color between red and blue out of it: shawn::Vec PropertyTagColorVecTask::PropertyTagColorVec:: value( double, const PropertyStackshawn::Vec&, const Element& e) const throw() { shawn::Vec out;
//Looking for the provided (double) tag:
double tagval = read_tag(e);
if(tagval < 0.0)
{
// Tag not available or not compatible, so the node gets a gray color:
out = shawn::Vec(0.5, 0.5, 0.5);
}
else
{
// Tag available, red for a tag-value of 0.0 and blue for a value of 1.0:
out = shawn::Vec(1.0-tagval, 0.0, tagval);
}
return out;
}
As shown above, our helper method returns a negative value, if the tag is not available, of a wrong type or has a negative value (we assume a tag value > 0 here). We return a gray color if this is the case. If the tag is available and compatible, we generate the color between red (tagval = 0) and blue (tagval = 1). Let's look at the helper method: double PropertyTagColorVecTask::PropertyTagColorVec:: read_tag(const Element& e) const throw() { const DrawableNode* dn = dynamic_cast<const DrawableNode*>(&e);
const shawn::DoubleTag *dtag = NULL;
if(dn != NULL)
{
ConstTagHandle taghandle = dn->node().find_tag(tag_);
if(taghandle != NULL)
{
dtag = dynamic_cast<const DoubleTag*>(taghandle.get());
}
}
double tval = -1.0;
if(dtag!=NULL)
{
tval = dtag->value();
if(tval > 1.0)
tval = 1.0;
}
return tval;
}
It casts the 'element' parameter to a drawable node. If this is possible (so if it really is a node) it looks for the tag and reads it's value, which is returned afterwards (or -1.0 if anything is wrong). Now, the new property is implemented completely, but we still have to implement the property's task. For that, we add the following definitions to the header file (in place of the 'Task member' comment): public: PropertyTagColorVecTask(); virtual ~PropertyTagColorVecTask(); virtual std::string name( void ) const throw(); virtual std::string description( void ) const throw(); protected: virtual ConstPropertyHandle create_property( shawn::SimulationController& sc ) throw( std::runtime_error ); Besides constructor, destructor and ID (name/description), this task needs a create_property method to generate instances of our new property. These methods are implemented in the .cpp file as shown below: PropertyTagColorVecTask:: PropertyTagColorVecTask() {} PropertyTagColorVecTask:: ~PropertyTagColorVecTask() {} std::string PropertyTagColorVecTask:: name( void ) const throw() { return "vis_tag_color_vec"; } std::string PropertyTagColorVecTask:: description( void ) const throw() { return "create a tag-dependend vector"; }
ConstPropertyHandle
PropertyTagColorVecTask::
create_property( shawn::SimulationController& sc )
throw( std::runtime_error )
{
std::string tagname = sc.environment().required_string_param("dynamictag");
PropertyTagColorVec* rtc = new PropertyTagColorVec(NULL, tagname);
try {
fill_default_params(*rtc,sc);
}
catch( std::runtime_error& ) {
delete rtc;
throw;
}
return rtc;
}
In create_property, we read out the required dynamictag parameter (which holds the name of the tag we want to use) and create the new property. With this, we finished the implementation of the new property task. But we can't run the task before it was added to a task keeper. For vector property tasks, this is done inside the 'vis_properties_vec_init.cpp' file in the 'init_vis_vec_properties' method. Just add the line sc.simulation_task_keeper_w().add( new PropertyTagColorVecTask ); here, and we are done.
To use the new property, configure your nodes to use a color property of the new type by for example adding vis_tag_color_vec elem_regex=node.* dynamictag=Battery prop=background prio=2 to the configuration file, and provide (in this example) a double tag called 'Battery' with values between 0 and 1. A sample implementation of this tutorial is provided by Vis.