diff --git a/fuse/package.xml b/fuse/package.xml
index 559361cd3..3a74fd374 100644
--- a/fuse/package.xml
+++ b/fuse/package.xml
@@ -19,6 +19,7 @@
fuse_optimizers
fuse_publishers
fuse_variables
+ fuse_viz
diff --git a/fuse_viz/CMakeLists.txt b/fuse_viz/CMakeLists.txt
new file mode 100644
index 000000000..51c111519
--- /dev/null
+++ b/fuse_viz/CMakeLists.txt
@@ -0,0 +1,109 @@
+cmake_minimum_required(VERSION 2.8.3)
+project(fuse_viz)
+
+set(build_depends
+ fuse_msgs
+ fuse_variables
+ rviz
+)
+
+find_package(catkin REQUIRED COMPONENTS
+ ${build_depends}
+)
+
+find_package(Boost REQUIRED COMPONENTS graph)
+
+find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
+set(QT_LIBRARIES Qt5::Widgets)
+
+add_definitions(-DQT_NO_KEYWORDS)
+
+###########
+## Build ##
+###########
+
+add_compile_options(-std=c++14 -Wall -Werror)
+
+catkin_package(
+ INCLUDE_DIRS include
+ LIBRARIES ${PROJECT_NAME}
+ CATKIN_DEPENDS
+ ${build_depends}
+ DEPENDS
+ Boost
+ QT
+)
+
+qt5_wrap_cpp(moc_files
+ include/fuse_viz/serialized_graph_display.h
+)
+
+set(source_files
+ src/serialized_graph_display.cpp
+ ${moc_files}
+)
+
+add_library(${PROJECT_NAME}
+ ${source_files}
+)
+add_dependencies(${PROJECT_NAME}
+ ${catkin_EXPORTED_TARGETS}
+)
+target_include_directories(${PROJECT_NAME}
+ PUBLIC
+ include
+ ${catkin_INCLUDE_DIRS}
+)
+target_link_libraries(${PROJECT_NAME}
+ ${catkin_LIBRARIES}
+ ${QT_LIBRARIES}
+)
+
+add_executable(serialized_graph_dot_writer_node
+ src/serialized_graph_dot_writer.cpp
+ src/serialized_graph_dot_writer_node.cpp
+)
+add_dependencies(serialized_graph_dot_writer_node
+ ${catkin_EXPORTED_TARGETS}
+)
+target_include_directories(serialized_graph_dot_writer_node
+ PUBLIC
+ include
+ ${catkin_INCLUDE_DIRS}
+)
+target_link_libraries(serialized_graph_dot_writer_node
+ ${catkin_LIBRARIES}
+ ${Boost_LIBRARIES}
+)
+
+#############
+## Install ##
+#############
+
+install(TARGETS ${PROJECT_NAME} serialized_graph_dot_writer_node
+ ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+ LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+ RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
+)
+
+install(DIRECTORY include/${PROJECT_NAME}/
+ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
+)
+
+install(
+ FILES rviz_plugins.xml
+ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
+)
+
+#############
+## Testing ##
+#############
+
+if(CATKIN_ENABLE_TESTING)
+ find_package(roslint REQUIRED)
+
+ # Lint tests
+ set(ROSLINT_CPP_OPTS "--filter=-build/c++11,-runtime/references")
+ roslint_cpp()
+ roslint_add_test()
+endif()
diff --git a/fuse_viz/include/fuse_viz/serialized_graph_display.h b/fuse_viz/include/fuse_viz/serialized_graph_display.h
new file mode 100644
index 000000000..10a8a1c3c
--- /dev/null
+++ b/fuse_viz/include/fuse_viz/serialized_graph_display.h
@@ -0,0 +1,104 @@
+/*
+ * Software License Agreement (BSD License)
+ *
+ * Copyright (c) 2019, Clearpath Robotics
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FUSE_VIZ_SERIALIZED_GRAPH_DISPLAY_H
+#define FUSE_VIZ_SERIALIZED_GRAPH_DISPLAY_H
+
+#ifndef Q_MOC_RUN
+#include
+#include
+
+#include
+#endif // Q_MOC_RUN
+
+#include
+
+namespace Ogre
+{
+class SceneNode;
+}
+
+namespace rviz
+{
+
+class Object;
+
+class BoolProperty;
+class FloatProperty;
+
+/**
+ * @brief An rviz dispaly for fuse_msgs::SerializedGraph messages.
+ */
+class SerializedGraphDisplay : public MessageFilterDisplay
+{
+ Q_OBJECT
+public:
+ SerializedGraphDisplay();
+
+ ~SerializedGraphDisplay() override;
+
+ void reset() override;
+
+protected:
+ void onInitialize() override;
+
+ void onEnable() override;
+
+ void onDisable() override;
+
+private Q_SLOTS:
+ void updateDrawVariablesAxesProperty();
+
+ void updateScaleProperty();
+
+private:
+ void clear();
+
+ void processMessage(const fuse_msgs::SerializedGraph::ConstPtr& msg) override;
+
+ Ogre::SceneNode* root_node_;
+ Ogre::SceneNode* variables_axes_node_;
+ Ogre::SceneNode* variables_spheres_node_;
+
+ std::vector