diff --git a/res/icons/icons.qrc b/res/icons/icons.qrc
index cfbe0e556023429890485c14b43b9779a4108ac8..1e3016df94ab4c91c9f41ed191f800230a3383a6 100644
--- a/res/icons/icons.qrc
+++ b/res/icons/icons.qrc
@@ -28,6 +28,7 @@
     <file alias="settings_display_icon">noun_38960_cc.svg</file>
     <file alias="settings_app_icon">noun_42425_cc.svg</file>
     <file alias="settings_network_icon">noun_62146_cc.svg</file>
+    <file alias="explorer_icon">noun_101791_cc.svg</file>
     <file alias="connected">connected.svg</file>
     <file alias="weak_connect">weak_connect.svg</file>
     <file alias="disconnected">disconnected.svg</file>
diff --git a/res/icons/noun_101791_cc.svg b/res/icons/noun_101791_cc.svg
new file mode 100644
index 0000000000000000000000000000000000000000..dabc0b049409edc3e5ae3670064c9aadcdf30d80
--- /dev/null
+++ b/res/icons/noun_101791_cc.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 100 125"
+   enable-background="new 0 0 100 100"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="noun_101791_cc.svg"><metadata
+     id="metadata14"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+     id="defs12" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="777"
+     inkscape:window-height="480"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="1.888"
+     inkscape:cx="50"
+     inkscape:cy="62.5"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" /><path
+     d="M 77.891855,62.286039 C 83.30181,41.246432 70.585007,19.72692 49.546735,14.316965 28.504455,8.9056726 6.9862794,21.622477 1.5749866,42.662085 -3.8349685,63.703028 8.8804994,85.219867 29.921443,90.632496 50.959714,96.042452 72.480563,83.326984 77.891855,62.286039 Z M 32.585649,80.269764 C 17.258111,76.328931 7.9968867,60.651155 11.937719,45.324953 15.879889,29.998751 31.554991,20.736191 46.879857,24.677023 62.207396,28.617856 71.469956,44.294295 67.527786,59.620497 63.585617,74.946699 47.913188,84.210597 32.585649,80.269764 Z m 64.940859,27.323826 c 0.0021,-0.001 0.0043,-0.002 0.0054,-0.002 -0.01741,0.0241 -0.04009,0.0494 -0.0588,0.0722 -0.01602,0.0227 -0.03074,0.0414 -0.04676,0.0642 -0.0022,0.001 -0.0043,0.001 -0.0064,0.004 -0.201856,0.24597 -0.458522,0.54942 -0.660378,0.75528 l -0.815433,0.83148 c -2.364767,2.41156 -5.464764,3.2056 -6.927202,1.77257 L 65.622872,88.163459 c 1.263259,-1.046699 2.932899,-2.573304 3.693528,-3.272441 l -0.0027,-0.0013 c 2.116125,-1.914271 2.855365,-3.03182 3.265757,-3.420823 0.818111,-0.772661 2.018541,-2.439628 2.418239,-2.870071 l 23.393699,22.925756 c 1.285982,1.26326 0.864889,3.82855 -0.8649,6.069 z M 30.801046,36.40862 c -5.951352,4.030396 -10.322628,11.609948 -8.509952,18.799161 0.661707,2.618755 -3.222981,3.823195 -3.883351,1.209787 -2.344715,-9.29598 2.997065,-18.605328 10.394814,-23.616923 2.211037,-1.497195 4.201505,2.116126 1.998489,3.607975 z"
+     id="path4"
+     inkscape:connector-curvature="0" /></svg>
\ No newline at end of file
diff --git a/res/ui/explorer_tab.ui b/res/ui/explorer_tab.ui
new file mode 100644
index 0000000000000000000000000000000000000000..58aa9891db98f11f4f66fcf45a3be4913937e123
--- /dev/null
+++ b/res/ui/explorer_tab.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExplorerTabWidget</class>
+ <widget class="QWidget" name="ExplorerTabWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>522</width>
+    <height>442</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="SearchUserWidget" name="search_user_widget" native="true"/>
+   </item>
+   <item>
+    <widget class="ExplorerView" name="graphicsView">
+     <property name="viewportUpdateMode">
+      <enum>QGraphicsView::BoundingRectViewportUpdate</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="topMargin">
+      <number>6</number>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Steps</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSlider" name="steps_slider">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="tickPosition">
+        <enum>QSlider::TicksBothSides</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="button_go">
+       <property name="text">
+        <string>Go</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>ExplorerView</class>
+   <extends>QGraphicsView</extends>
+   <header>sakia.gui.views</header>
+  </customwidget>
+  <customwidget>
+   <class>SearchUserWidget</class>
+   <extends>QWidget</extends>
+   <header>sakia.gui.widgets.search_user</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources>
+  <include location="../icons/icons.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+  <slot>reset()</slot>
+  <slot>search()</slot>
+  <slot>select_node()</slot>
+ </slots>
+</ui>
diff --git a/res/ui/search_user_view.ui b/res/ui/search_user_view.ui
new file mode 100644
index 0000000000000000000000000000000000000000..ac85b8f6315e27911a64ed37dd722ee9bae882e8
--- /dev/null
+++ b/res/ui/search_user_view.ui
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SearchUserWidget</class>
+ <widget class="QWidget" name="SearchUserWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>44</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QComboBox" name="combobox_search">
+     <property name="editable">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="button_reset">
+     <property name="maximumSize">
+      <size>
+       <width>85</width>
+       <height>27</height>
+      </size>
+     </property>
+     <property name="toolTip">
+      <string>Center the view on me</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="icon">
+      <iconset resource="../icons/icons.qrc">
+       <normaloff>:/icons/home_icon</normaloff>:/icons/home_icon</iconset>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../icons/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/res/ui/wot_tab.ui b/res/ui/wot_tab.ui
index 4175fed16225067b2a604095fda034c93f571fb3..d71654c11480961021b3679b84a89243a1b1e3b2 100644
--- a/res/ui/wot_tab.ui
+++ b/res/ui/wot_tab.ui
@@ -14,33 +14,6 @@
    <string>Form</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0">
-    <widget class="QComboBox" name="comboBoxSearch">
-     <property name="editable">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="1">
-    <widget class="QPushButton" name="pushButtonReset">
-     <property name="maximumSize">
-      <size>
-       <width>85</width>
-       <height>27</height>
-      </size>
-     </property>
-     <property name="toolTip">
-      <string>Center the view on me</string>
-     </property>
-     <property name="text">
-      <string/>
-     </property>
-     <property name="icon">
-      <iconset resource="../icons/icons.qrc">
-       <normaloff>:/icons/home_icon</normaloff>:/icons/home_icon</iconset>
-     </property>
-    </widget>
-   </item>
    <item row="1" column="0" colspan="2">
     <widget class="WotView" name="graphicsView">
      <property name="viewportUpdateMode">
@@ -48,6 +21,9 @@
      </property>
     </widget>
    </item>
+   <item row="0" column="0" colspan="2">
+    <widget class="SearchUserWidget" name="search_user_widget" native="true"/>
+   </item>
   </layout>
  </widget>
  <customwidgets>
@@ -56,44 +32,17 @@
    <extends>QGraphicsView</extends>
    <header>sakia.gui.views.wot</header>
   </customwidget>
+  <customwidget>
+   <class>SearchUserWidget</class>
+   <extends>QWidget</extends>
+   <header>sakia.gui.widgets.search_user</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources>
   <include location="../icons/icons.qrc"/>
  </resources>
- <connections>
-  <connection>
-   <sender>pushButtonReset</sender>
-   <signal>clicked()</signal>
-   <receiver>WotTabWidget</receiver>
-   <slot>reset()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>516</x>
-     <y>23</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>284</x>
-     <y>198</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>comboBoxSearch</sender>
-   <signal>currentIndexChanged(int)</signal>
-   <receiver>WotTabWidget</receiver>
-   <slot>select_node()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>215</x>
-     <y>22</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>260</x>
-     <y>220</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
+ <connections/>
  <slots>
   <slot>reset()</slot>
   <slot>search()</slot>
diff --git a/src/sakia/core/graph/base_graph.py b/src/sakia/core/graph/base_graph.py
index 85d0bae02b69a1202ced19bce21cec3bd9accea4..ee6443bacc72fb264a126f11821f7b97ba9a5782 100644
--- a/src/sakia/core/graph/base_graph.py
+++ b/src/sakia/core/graph/base_graph.py
@@ -4,7 +4,7 @@ import networkx
 from PyQt5.QtCore import QLocale, QDateTime, QObject
 from sakia.tools.exceptions import NoPeerAvailable
 from sakia.core.net.network import MAX_CONFIRMATIONS
-from .constants import ArcStatus, NodeStatus
+from .constants import EdgeStatus, NodeStatus
 
 
 class BaseGraph(QObject):
@@ -35,9 +35,9 @@ class BaseGraph(QObject):
         # display validity status
         ts = time.time()
         if (time.time() - cert_time) > arc_strong:
-            return ArcStatus.WEAK
+            return EdgeStatus.WEAK
         else:
-            return ArcStatus.STRONG
+            return EdgeStatus.STRONG
 
     async def node_status(self, node_identity, account_identity):
         """
@@ -159,7 +159,7 @@ class BaseGraph(QObject):
         Add identity as a new node in graph
         :param identity identity: identity instance
         :param int status:  Optional, default=None, Node status (see sakia.gui.views.wot)
-        :param list arcs:  Optional, default=None, List of arcs (certified by identity)
+        :param list edges:  Optional, default=None, List of edges (certified by identity)
         :param list connected:  Optional, default=None, Public key list of the connected nodes around the identity
         :return:
         """
diff --git a/src/sakia/core/graph/constants.py b/src/sakia/core/graph/constants.py
index 56930a665c8402aafce0971c6be666500b4ab827..0509ef036255884f5825211603af1812ac0d47b2 100644
--- a/src/sakia/core/graph/constants.py
+++ b/src/sakia/core/graph/constants.py
@@ -1,7 +1,7 @@
 from enum import Enum
 
 
-class ArcStatus(Enum):
+class EdgeStatus(Enum):
     WEAK = 0
     STRONG = 1
 
diff --git a/src/sakia/core/graph/explorer_graph.py b/src/sakia/core/graph/explorer_graph.py
index 73fa6f8adc654e52301e91a850f8dc9854a673a6..420b4b48a4cc9b106188fd30cb9d3cbad434231e 100644
--- a/src/sakia/core/graph/explorer_graph.py
+++ b/src/sakia/core/graph/explorer_graph.py
@@ -3,12 +3,13 @@ import networkx
 import asyncio
 from PyQt5.QtCore import pyqtSignal
 from .base_graph import BaseGraph
-from sakia.core.graph.constants import ArcStatus, NodeStatus
+from sakia.core.graph.constants import EdgeStatus, NodeStatus
 
 
 class ExplorerGraph(BaseGraph):
 
     graph_changed = pyqtSignal()
+    current_identity_changed = pyqtSignal(str)
 
     def __init__(self, app, community, nx_graph=None):
         """
@@ -21,6 +22,7 @@ class ExplorerGraph(BaseGraph):
         super().__init__(app, community, nx_graph)
         self.exploration_task = None
         self.explored_identity = None
+        self.steps = 0
 
     def start_exploration(self, identity, steps):
         """
@@ -29,11 +31,13 @@ class ExplorerGraph(BaseGraph):
         :param int steps: The number of steps from identity to explore
         """
         if self.exploration_task:
-            if self.explored_identity is not identity:
+            if self.explored_identity is not identity or steps != self.steps:
                 self.exploration_task.cancel()
             else:
                 return
+        self.nx_graph.clear()
         self.explored_identity = identity
+        self.steps = steps
         self.exploration_task = asyncio.ensure_future(self._explore(identity, steps))
 
     def stop_exploration(self):
@@ -42,6 +46,7 @@ class ExplorerGraph(BaseGraph):
         """
         if self.exploration_task:
             self.exploration_task.cancel()
+            self.exploration_task = None
 
     async def _explore(self, identity, steps):
         """
@@ -56,13 +61,18 @@ class ExplorerGraph(BaseGraph):
         explored = []
         explorable = {0: [identity]}
         current_identity = identity
+        self.nx_graph.clear()
+        self.add_identity(current_identity, NodeStatus.HIGHLIGHTED)
+        self.graph_changed.emit()
         for step in range(1, steps + 1):
             explorable[step] = []
 
         for step in range(0, steps):
             while len(explorable[step]) > 0:
+                current_identity = explorable[step].pop()
                 # for each pubkey connected...
                 if current_identity not in explored:
+                    self.current_identity_changed.emit(current_identity.pubkey)
                     self.add_identity(current_identity, NodeStatus.NEUTRAL)
                     logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
                     self.graph_changed.emit()
@@ -86,4 +96,4 @@ class ExplorerGraph(BaseGraph):
                     explored.append(current_identity)
                     logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
                     self.graph_changed.emit()
-                current_identity = explorable[step].pop()
\ No newline at end of file
+        self.current_identity_changed.emit("")
diff --git a/src/sakia/core/registry/identity.py b/src/sakia/core/registry/identity.py
index e36f11e105d509a1344a3127bce6f180421821d8..ce0e1d7757c284c59c612c4f4e7b2b7964911826 100644
--- a/src/sakia/core/registry/identity.py
+++ b/src/sakia/core/registry/identity.py
@@ -299,7 +299,7 @@ class Identity(QObject):
                 certifiers.append(certifier)
         except ValueError as e:
             if '404' in str(e):
-                logging.debug('bma.wot.CertifiersOf request error: {0}'.format(str(e)))
+                logging.debug('bma.wot.CertifiersOf request error')
             else:
                 logging.debug(str(e))
         except NoPeerAvailable as e:
@@ -392,7 +392,7 @@ class Identity(QObject):
                 certified_list.append(certified)
         except ValueError as e:
             if '404' in str(e):
-                logging.debug('bma.wot.CertifiersOf request error')
+                logging.debug('bma.wot.CertifiedBy request error')
         except NoPeerAvailable as e:
             logging.debug(str(e))
 
diff --git a/src/sakia/gui/community_view.py b/src/sakia/gui/community_view.py
index ac2261d00ae5da048449210deb291041e0ca96ed..5db08b0bb898ec57593cb1ac92cd9937ce34e607 100644
--- a/src/sakia/gui/community_view.py
+++ b/src/sakia/gui/community_view.py
@@ -11,14 +11,15 @@ from PyQt5.QtCore import pyqtSlot, QDateTime, QLocale, QEvent, QT_TRANSLATE_NOOP
 from PyQt5.QtGui import QIcon
 from PyQt5.QtWidgets import QWidget, QMessageBox, QDialog, QPushButton, QTabBar, QAction
 
-from sakia.gui.graphs.wot_tab import WotTabWidget
-from sakia.gui.widgets import toast
-from sakia.gui.widgets.dialogs import QAsyncMessageBox
+from .graphs.wot_tab import WotTabWidget
+from .widgets import toast
+from .widgets.dialogs import QAsyncMessageBox
 from .certifications_tab import CertificationsTabWidget
 from .identities_tab import IdentitiesTabWidget
 from .informations_tab import InformationsTabWidget
 from .network_tab import NetworkTabWidget
 from .transactions_tab import TransactionsTabWidget
+from .graphs.explorer_tab import ExplorerTabWidget
 from ..gen_resources.community_view_uic import Ui_CommunityWidget
 from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
 from ..tools.exceptions import MembershipNotFoundError, LookupFailureError, NoPeerAvailable
@@ -36,6 +37,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
     _tab_network_label = QT_TRANSLATE_NOOP("CommunityWidget", "Network")
     _tab_informations_label = QT_TRANSLATE_NOOP("CommunityWidget", "Informations")
     _action_showinfo_text = QT_TRANSLATE_NOOP("CommunityWidget", "Show informations")
+    _action_explore_text = QT_TRANSLATE_NOOP("CommunityWidget", "Explore the Web of Trust")
     _action_publish_uid_text = QT_TRANSLATE_NOOP("CommunityWidget", "Publish UID")
     _action_revoke_uid_text = QT_TRANSLATE_NOOP("CommunityWidget", "Revoke UID")
 
@@ -59,10 +61,12 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         self.tab_informations = InformationsTabWidget(self.app)
         self.tab_certifications = CertificationsTabWidget(self.app)
         self.tab_network = NetworkTabWidget(self.app)
+        self.tab_explorer = ExplorerTabWidget(self.app)
 
         self.action_publish_uid = QAction(self.tr(CommunityWidget._action_publish_uid_text), self)
         self.action_revoke_uid = QAction(self.tr(CommunityWidget._action_revoke_uid_text), self)
         self.action_showinfo = QAction(self.tr(CommunityWidget._action_showinfo_text), self)
+        self.action_explorer = QAction(self.tr(CommunityWidget._action_explore_text), self)
 
         super().setupUi(self)
 
@@ -94,6 +98,11 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
                                     QIcon(":/icons/informations_icon"), self.tr("Informations")))
         self.toolbutton_menu.addAction(action_showinfo)
 
+        action_showexplorer = QAction(self.tr("Show explorer"), self.toolbutton_menu)
+        action_showexplorer.triggered.connect(lambda : self.show_closable_tab(self.tab_explorer,
+                                    QIcon(":/icons/explorer_icon"), self.tr("Explorer")))
+        self.toolbutton_menu.addAction(action_showexplorer)
+
         self.action_publish_uid.triggered.connect(self.publish_uid)
         self.toolbutton_menu.addAction(self.action_publish_uid)
 
@@ -127,6 +136,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         self.tab_identities.change_account(account, self.password_asker)
         self.tab_history.change_account(account, self.password_asker)
         self.tab_informations.change_account(account)
+        self.tab_explorer.change_account(account, self.password_asker)
 
     def change_community(self, community):
         self.cancel_once_tasks()
@@ -136,6 +146,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         self.tab_history.change_community(community)
         self.tab_identities.change_community(community)
         self.tab_informations.change_community(community)
+        self.tab_explorer.change_community(community)
 
         if self.community:
             self.community.network.new_block_mined.disconnect(self.refresh_block)
diff --git a/src/sakia/gui/graphs/explorer_tab.py b/src/sakia/gui/graphs/explorer_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..75321719cb4994d532b1b4e070209e5a437735a5
--- /dev/null
+++ b/src/sakia/gui/graphs/explorer_tab.py
@@ -0,0 +1,120 @@
+import logging
+
+from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP
+
+from ucoinpy.api import bma
+
+from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task
+from ...tools.exceptions import NoPeerAvailable
+from ...core.graph import ExplorerGraph
+from .graph_tab import GraphTabWidget
+from ...gen_resources.explorer_tab_uic import Ui_ExplorerTabWidget
+
+
+class ExplorerTabWidget(GraphTabWidget, Ui_ExplorerTabWidget):
+
+    money_sent = pyqtSignal()
+
+    def __init__(self, app):
+        """
+        :param sakia.core.app.Application app: Application instance
+        """
+        # construct from qtDesigner
+        super().__init__(app)
+        self.setupUi(self)
+        self.search_user_widget.init(app)
+
+        self.set_scene(self.graphicsView.scene())
+
+        self.account = None
+        self.community = None
+        self.password_asker = None
+        self.graph = None
+        self.app = app
+        self.draw_task = None
+
+        # nodes list for menu from search
+        self.nodes = list()
+
+        # create node metadata from account
+        self._current_identity = None
+        self.button_go.clicked.connect(self.go_clicked)
+        self.search_user_widget.identity_selected.connect(self.draw_graph)
+        self.search_user_widget.reset.connect(self.reset)
+
+    def cancel_once_tasks(self):
+        cancel_once_task(self, self.refresh_informations_frame)
+        cancel_once_task(self, self.reset)
+
+    def change_account(self, account, password_asker):
+        self.search_user_widget.change_account(account)
+        self.account = account
+        self.password_asker = password_asker
+
+    def change_community(self, community):
+        self.community = community
+        if self.graph:
+            self.graph.stop_exploration()
+        self.graph = ExplorerGraph(self.app, self.community)
+        self.graph.graph_changed.connect(self.refresh)
+        self.search_user_widget.change_community(community)
+        self.graph.current_identity_changed.connect(self.graphicsView.scene().update_current_identity)
+        self.reset()
+
+    def go_clicked(self):
+        if self.graph:
+            self.graph.stop_exploration()
+            self.draw_graph(self._current_identity)
+
+    def draw_graph(self, identity):
+        """
+        Draw community graph centered on the identity
+
+        :param sakia.core.registry.Identity identity: Graph node identity
+        """
+        logging.debug("Draw graph - " + identity.uid)
+
+        if self.community:
+            #Connect new identity
+            if self._current_identity != identity:
+                self._current_identity = identity
+
+            self.graph.start_exploration(identity, self.steps_slider.value())
+
+            # draw graph in qt scene
+            self.graphicsView.scene().clear()
+            self.graphicsView.scene().update_wot(self.graph.nx_graph, identity, self.steps_slider.maximum())
+
+    def refresh(self):
+        """
+        Refresh graph scene to current metadata
+        """
+        if self._current_identity:
+            # draw graph in qt scene
+            self.graphicsView.scene().update_wot(self.graph.nx_graph, self._current_identity, self.steps_slider.maximum())
+        else:
+            self.reset()
+
+    @once_at_a_time
+    @asyncify
+    async def reset(self, checked=False):
+        """
+        Reset graph scene to wallet identity
+        """
+        if self.account and self.community:
+            parameters = await self.community.parameters()
+            self.steps_slider.setMaximum(parameters['stepMax'])
+            self.steps_slider.setValue(int(0.33 * parameters['stepMax']))
+            identity = await self.account.identity(self.community)
+            self.draw_graph(identity)
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self.refresh()
+        return super().changeEvent(event)
diff --git a/src/sakia/gui/graphs/graph_tab.py b/src/sakia/gui/graphs/graph_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b4fee5cf6f54a5aabdd71522ee9355c546e3ca4
--- /dev/null
+++ b/src/sakia/gui/graphs/graph_tab.py
@@ -0,0 +1,214 @@
+from PyQt5.QtWidgets import QWidget, QDialog
+from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal
+
+from ...tools.exceptions import MembershipNotFoundError
+from ...tools.decorators import asyncify, once_at_a_time
+from ...core.registry import BlockchainState
+from ...gui.member import MemberDialog
+from ...gui.certification import CertificationDialog
+from ...gui.transfer import TransferMoneyDialog
+from ...gui.contact import ConfigureContactDialog
+
+
+class GraphTabWidget(QWidget):
+
+    money_sent = pyqtSignal()
+    def __init__(self, app):
+        """
+        :param sakia.core.app.Application app: Application instance
+        """
+        super().__init__()
+
+        self.password_asker = None
+        self.app = app
+
+    def set_scene(self, scene):
+        # add scene events
+        scene.node_clicked.connect(self.handle_node_click)
+        scene.node_signed.connect(self.sign_node)
+        scene.node_transaction.connect(self.send_money_to_node)
+        scene.node_contact.connect(self.add_node_as_contact)
+        scene.node_member.connect(self.identity_informations)
+        scene.node_copy_pubkey.connect(self.copy_node_pubkey)
+
+    @once_at_a_time
+    @asyncify
+    async def refresh_informations_frame(self):
+        parameters = self.community.parameters
+        try:
+            identity = await self.account.identity(self.community)
+            membership = identity.membership(self.community)
+            renew_block = membership['blockNumber']
+            last_renewal = self.community.get_block(renew_block)['medianTime']
+            expiration = last_renewal + parameters['sigValidity']
+        except MembershipNotFoundError:
+            last_renewal = None
+            expiration = None
+
+        certified = await identity.unique_valid_certified_by(self.app.identities_registry, self.community)
+        certifiers = await identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
+        if last_renewal and expiration:
+            date_renewal = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
+            )
+            date_expiration = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
+            )
+
+            if self.account.pubkey in self.community.members_pubkeys():
+                # set infos in label
+                self.label_general.setText(
+                    self.tr("""
+                    <table cellpadding="5">
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    </table>
+                    """).format(
+                        self.account.name, self.account.pubkey,
+                        self.tr("Membership"),
+                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
+                        self.tr("Your web of trust"),
+                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
+                                                                                             len(certified))
+                    )
+                )
+            else:
+                # set infos in label
+                self.label_general.setText(
+                    self.tr("""
+                    <table cellpadding="5">
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                    </table>
+                    """).format(
+                        self.account.name, self.account.pubkey,
+                        self.tr("Not a member"),
+                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
+                        self.tr("Your web of trust"),
+                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
+                                                                                             len(certified))
+                    )
+                )
+        else:
+            # set infos in label
+            self.label_general.setText(
+                self.tr("""
+                <table cellpadding="5">
+                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                <tr><td align="right"><b>{:}</b></td></tr>
+                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+                </table>
+                """).format(
+                    self.account.name, self.account.pubkey,
+                    self.tr("Not a member"),
+                    self.tr("Your web of trust"),
+                    self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
+                                                                                         len(certified))
+                )
+            )
+
+    @pyqtSlot(str, dict)
+    def handle_node_click(self, pubkey, metadata):
+        self.draw_graph(
+            self.app.identities_registry.from_handled_data(
+                metadata['text'],
+                pubkey,
+                None,
+                BlockchainState.VALIDATED,
+                self.community
+            )
+        )
+
+    @once_at_a_time
+    @asyncify
+    async def draw_graph(self, identity):
+        """
+        Draw community graph centered on the identity
+
+        :param sakia.core.registry.Identity identity: Graph node identity
+        """
+        pass
+
+    @once_at_a_time
+    @asyncify
+    async def reset(self, checked=False):
+        """
+        Reset graph scene to wallet identity
+        """
+        pass
+
+    def refresh(self):
+        """
+        Refresh graph scene to current metadata
+        """
+        pass
+
+    def identity_informations(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        dialog = MemberDialog(self.app, self.account, self.community, identity)
+        dialog.exec_()
+
+    @asyncify
+    async def sign_node(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        await CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
+                                             self.community, identity)
+
+    @asyncify
+    async def send_money_to_node(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        result = await TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
+                                                            self.community, identity)
+        if result == QDialog.Accepted:
+            self.money_sent.emit()
+
+    def copy_node_pubkey(self, pubkey):
+        cb = self.app.qapp.clipboard()
+        cb.clear(mode=cb.Clipboard)
+        cb.setText(pubkey, mode=cb.Clipboard)
+
+    def add_node_as_contact(self, pubkey, metadata):
+        # check if contact already exists...
+        if pubkey == self.account.pubkey \
+                or pubkey in [contact['pubkey'] for contact in self.account.contacts]:
+            return False
+        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
+                                                                      'pubkey': pubkey,
+                                                                      })
+        result = dialog.exec_()
+        if result == QDialog.Accepted:
+            self.window().refresh_contacts()
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self._auto_refresh(None)
+            self.refresh()
+        return super().changeEvent(event)
diff --git a/src/sakia/gui/graphs/wot_tab.py b/src/sakia/gui/graphs/wot_tab.py
index ac9b934e6f0239b4bf0fe49436f8c2606791f93b..ce12bd290e659bde210edc643d3f2d9b80427e2b 100644
--- a/src/sakia/gui/graphs/wot_tab.py
+++ b/src/sakia/gui/graphs/wot_tab.py
@@ -1,54 +1,30 @@
 import logging
 import asyncio
 
-from PyQt5.QtWidgets import QWidget, QComboBox, QDialog
-from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal, QT_TRANSLATE_NOOP
-from ucoinpy.api import bma
-
-from ...tools.exceptions import MembershipNotFoundError
+from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP
 from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task
 from ...core.graph import WoTGraph
-from ...core.registry import BlockchainState
-from ..member import MemberDialog
-from ..certification import CertificationDialog
-from ..transfer import TransferMoneyDialog
-from ..contact import ConfigureContactDialog
 from ...gen_resources.wot_tab_uic import Ui_WotTabWidget
-from ..widgets.busy import Busy
-from ...tools.exceptions import NoPeerAvailable
+from ...gui.widgets.busy import Busy
+from .graph_tab import GraphTabWidget
 
 
-class WotTabWidget(QWidget, Ui_WotTabWidget):
+class WotTabWidget(GraphTabWidget, Ui_WotTabWidget):
 
     money_sent = pyqtSignal()
-    _search_placeholder = QT_TRANSLATE_NOOP("WotTabWidget", "Research a pubkey, an uid...")
 
     def __init__(self, app):
         """
         :param sakia.core.app.Application app: Application instance
         """
-        super().__init__()
+        super().__init__(app)
         # construct from qtDesigner
         self.setupUi(self)
-
-        # Default text when combo lineEdit is empty
-        self.comboBoxSearch.lineEdit().setPlaceholderText(self.tr(WotTabWidget._search_placeholder))
-        #  add combobox events
-        self.comboBoxSearch.lineEdit().returnPressed.connect(self.search)
-        # To fix a recall of the same item with different case,
-        # the edited text is not added in the item list
-        self.comboBoxSearch.setInsertPolicy(QComboBox.NoInsert)
-
+        self.search_user_widget.init(app)
         self.busy = Busy(self.graphicsView)
         self.busy.hide()
 
-        # add scene events
-        self.graphicsView.scene().node_clicked.connect(self.handle_node_click)
-        self.graphicsView.scene().node_signed.connect(self.sign_node)
-        self.graphicsView.scene().node_transaction.connect(self.send_money_to_node)
-        self.graphicsView.scene().node_contact.connect(self.add_node_as_contact)
-        self.graphicsView.scene().node_member.connect(self.identity_informations)
-        self.graphicsView.scene().node_copy_pubkey.connect(self.copy_node_pubkey)
+        self.set_scene(self.graphicsView.scene())
 
         self.account = None
         self.community = None
@@ -56,8 +32,8 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         self.app = app
         self.draw_task = None
 
-        # nodes list for menu from search
-        self.nodes = list()
+        self.search_user_widget.identity_selected.connect(self.draw_graph)
+        self.search_user_widget.reset.connect(self.reset)
 
         # create node metadata from account
         self._current_identity = None
@@ -69,15 +45,13 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
 
     def change_account(self, account, password_asker):
         self.cancel_once_tasks()
-        if self.account is not None:
-            self.account.certification_accepted.disconnect(self.refresh)
+        self.search_user_widget.change_account(account)
         self.account = account
         self.password_asker = password_asker
-        if self.account is not None:
-            self.account.certification_accepted.connect(self.refresh)
 
     def change_community(self, community):
         self.cancel_once_tasks()
+        self.search_user_widget.change_community(community)
         self._auto_refresh(community)
         self.community = community
         self.reset()
@@ -95,105 +69,13 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
             elif self.community:
                 self.community.network.new_block_mined.connect(self.refresh)
 
-    @once_at_a_time
-    @asyncify
-    async def refresh_informations_frame(self):
-        parameters = self.community.parameters
-        try:
-            identity = await self.account.identity(self.community)
-            membership = identity.membership(self.community)
-            renew_block = membership['blockNumber']
-            last_renewal = self.community.get_block(renew_block)['medianTime']
-            expiration = last_renewal + parameters['sigValidity']
-        except MembershipNotFoundError:
-            last_renewal = None
-            expiration = None
-
-        certified = await identity.unique_valid_certified_by(self.app.identities_registry, self.community)
-        certifiers = await identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
-        if last_renewal and expiration:
-            date_renewal = QLocale.toString(
-                QLocale(),
-                QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
-            )
-            date_expiration = QLocale.toString(
-                QLocale(),
-                QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
-            )
-
-            if self.account.pubkey in self.community.members_pubkeys():
-                # set infos in label
-                self.label_general.setText(
-                    self.tr("""
-                    <table cellpadding="5">
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    </table>
-                    """).format(
-                        self.account.name, self.account.pubkey,
-                        self.tr("Membership"),
-                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
-                        self.tr("Your web of trust"),
-                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                             len(certified))
-                    )
-                )
-            else:
-                # set infos in label
-                self.label_general.setText(
-                    self.tr("""
-                    <table cellpadding="5">
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    </table>
-                    """).format(
-                        self.account.name, self.account.pubkey,
-                        self.tr("Not a member"),
-                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
-                        self.tr("Your web of trust"),
-                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                             len(certified))
-                    )
-                )
-        else:
-            # set infos in label
-            self.label_general.setText(
-                self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                </table>
-                """).format(
-                    self.account.name, self.account.pubkey,
-                    self.tr("Not a member"),
-                    self.tr("Your web of trust"),
-                    self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                         len(certified))
-                )
-            )
-
-    @pyqtSlot(dict)
-    def handle_node_click(self, metadata):
-        self.draw_graph(
-            self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                metadata['id'],
-                None,
-                BlockchainState.VALIDATED,
-                self.community
-            )
-        )
-
     @once_at_a_time
     @asyncify
     async def draw_graph(self, identity):
         """
         Draw community graph centered on the identity
 
-        :param sakia.core.registry.Identity identity: Graph node identity
+        :param sakia.core.registry.Identity identity: Center identity
         """
         logging.debug("Draw graph - " + identity.uid)
         self.busy.show()
@@ -203,7 +85,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
 
             # create empty graph instance
             graph = WoTGraph(self.app, self.community)
-            await graph.initialize(identity_account, identity_account)
+            await graph.initialize(identity, identity_account)
             # draw graph in qt scene
             self.graphicsView.scene().update_wot(graph.nx_graph, identity)
 
@@ -212,7 +94,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
                 # add path from selected member to account member
                 path = await graph.get_shortest_path_to_identity(identity_account, identity)
                 if path:
-                    self.graphicsView.scene().update_path(path)
+                    self.graphicsView.scene().update_path(graph.nx_graph, path)
         self.busy.hide()
 
     @once_at_a_time
@@ -234,112 +116,6 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         else:
             self.reset()
 
-    @asyncify
-    async def search(self):
-        """
-        Search nodes when return is pressed in combobox lineEdit
-        """
-        text = self.comboBoxSearch.lineEdit().text()
-
-        if len(text) < 2:
-            return False
-        try:
-            response = await self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})
-
-            nodes = {}
-            for identity in response['results']:
-                nodes[identity['pubkey']] = identity['uids'][0]['uid']
-
-            if nodes:
-                self.nodes = list()
-                self.comboBoxSearch.clear()
-                self.comboBoxSearch.lineEdit().setText(text)
-                for pubkey, uid in nodes.items():
-                    self.nodes.append({'pubkey': pubkey, 'uid': uid})
-                    self.comboBoxSearch.addItem(uid)
-                self.comboBoxSearch.showPopup()
-        except NoPeerAvailable:
-            pass
-
-    def select_node(self, index):
-        """
-        Select node in graph when item is selected in combobox
-        """
-        if index < 0 or index >= len(self.nodes):
-            return False
-        node = self.nodes[index]
-        metadata = {'id': node['pubkey'], 'text': node['uid']}
-        self.draw_graph(
-            self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                metadata['id'],
-                None,
-                BlockchainState.VALIDATED,
-                self.community
-            )
-        )
-
-    def identity_informations(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        dialog = MemberDialog(self.app, self.account, self.community, identity)
-        dialog.exec_()
-
-    @asyncify
-    async def sign_node(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        await CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
-                                             self.community, identity)
-
-    @asyncify
-    async def send_money_to_node(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        result = await TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
-                                                            self.community, identity)
-        if result == QDialog.Accepted:
-            self.money_sent.emit()
-
-    def copy_node_pubkey(self, metadata):
-        cb = self.app.qapp.clipboard()
-        cb.clear(mode=cb.Clipboard)
-        cb.setText(metadata['id'], mode=cb.Clipboard)
-
-    def add_node_as_contact(self, metadata):
-        # check if contact already exists...
-        if metadata['id'] == self.account.pubkey \
-                or metadata['id'] in [contact['pubkey'] for contact in self.account.contacts]:
-            return False
-        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
-                                                                      'pubkey': metadata['id'],
-                                                                      })
-        result = dialog.exec_()
-        if result == QDialog.Accepted:
-            self.window().refresh_contacts()
-
-    def retranslateUi(self, widget):
-        """
-        Retranslate missing widgets from generated code
-        """
-        self.comboBoxSearch.lineEdit().setPlaceholderText(self.tr(WotTabWidget._search_placeholder))
-        super().retranslateUi(self)
-
     def resizeEvent(self, event):
         self.busy.resize(event.size())
         super().resizeEvent(event)
diff --git a/src/sakia/gui/views/__init__.py b/src/sakia/gui/views/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3ca1e9f7ff0ac7bd3d3e7c9e4d8344dd36a87a54 100644
--- a/src/sakia/gui/views/__init__.py
+++ b/src/sakia/gui/views/__init__.py
@@ -0,0 +1,2 @@
+from .explorer import ExplorerView
+from .wot import WotView
\ No newline at end of file
diff --git a/src/sakia/gui/views/edges/__init__.py b/src/sakia/gui/views/edges/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..188e3acfd2ccf101092227b52be070f696cd68c4
--- /dev/null
+++ b/src/sakia/gui/views/edges/__init__.py
@@ -0,0 +1,2 @@
+from .wot_edge import WotEdge
+from .explorer_edge import ExplorerEdge
\ No newline at end of file
diff --git a/src/sakia/gui/views/edges/base_edge.py b/src/sakia/gui/views/edges/base_edge.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a2ceb32870bfa6c2ffe421b62c4034e5150b21c
--- /dev/null
+++ b/src/sakia/gui/views/edges/base_edge.py
@@ -0,0 +1,26 @@
+from PyQt5.QtWidgets import QGraphicsLineItem
+from PyQt5.QtCore import Qt, QPointF
+
+
+class BaseEdge(QGraphicsLineItem):
+    def __init__(self, source_node, destination_node, metadata, nx_pos):
+        """
+        Create an arc between two nodes
+
+        :param str source_node: Source node id of the arc
+        :param str destination_node: Destination node id of the arc
+        :param dict metadata: Edge metadata
+        :param dict nx_pos: The position generated by nx_graph
+        """
+        super().__init__()
+
+        self.metadata = metadata
+        self.source = source_node
+        self.destination = destination_node
+
+        self.status = self.metadata['status']
+
+        self.source_point = QPointF(nx_pos[self.source][0], nx_pos[self.source][1])
+        self.destination_point = QPointF(nx_pos[self.destination][0], nx_pos[self.destination][1])
+
+        self.setAcceptedMouseButtons(Qt.NoButton)
diff --git a/src/sakia/gui/views/edges/explorer_edge.py b/src/sakia/gui/views/edges/explorer_edge.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f39bea9cc3fc8879eaa3575eadf0b5d2065b2c8
--- /dev/null
+++ b/src/sakia/gui/views/edges/explorer_edge.py
@@ -0,0 +1,154 @@
+from PyQt5.QtCore import Qt, QRectF, QLineF, QPointF, QSizeF, \
+                        qFuzzyCompare, QTimeLine
+from PyQt5.QtGui import QColor, QPen, QPolygonF
+import math
+from .base_edge import BaseEdge
+from ....core.graph.constants import EdgeStatus
+
+
+class ExplorerEdge(BaseEdge):
+    def __init__(self, source_node, destination_node, metadata, nx_pos, steps, steps_max):
+        """
+        Create an arc between two nodes
+
+        :param Node source_node: Source node of the arc
+        :param Node destination_node: Destination node of the arc
+        :param dict metadata: Arc metadata
+        :param dict nx_pos: The position generated by nx_graph
+        :param int steps: The steps from the center identity
+        :param int steps_max: The steps max of the graph
+        """
+        super().__init__(source_node, destination_node, metadata, nx_pos)
+
+        self.source_point = self.destination_point
+        self.steps = steps
+        self.steps_max = steps_max
+        self.highlighted = False
+
+        self.arrow_size = 5
+        #  cursor change on hover
+        self.setAcceptHoverEvents(True)
+        self.setZValue(0)
+        self._line_styles = {
+            EdgeStatus.STRONG: Qt.SolidLine,
+            EdgeStatus.WEAK: Qt.DashLine
+        }
+        self.timeline = None
+
+    @property
+    def line_style(self):
+        return self._line_styles[self.status]
+
+    # virtual function require subclassing
+    def boundingRect(self):
+        """
+        Return the bounding rectangle size
+
+        :return: QRectF
+        """
+        if not self.source or not self.destination:
+            return QRectF()
+        pen_width = 1.0
+        extra = (pen_width + self.arrow_size) / 2.0
+
+        return QRectF(
+            self.source_point, QSizeF(
+                self.destination_point.x() - self.source_point.x(),
+                self.destination_point.y() - self.source_point.y()
+            )
+        ).normalized().adjusted(
+            -extra,
+            -extra,
+            extra,
+            extra
+        )
+
+    def paint(self, painter, option, widget):
+        """
+        Customize line adding an arrow head
+
+        :param QPainter painter: Painter instance of the item
+        :param option:  Painter option of the item
+        :param widget:  Widget instance
+        """
+        if not self.source or not self.destination:
+            return
+        line = QLineF(self.source_point, self.destination_point)
+        if qFuzzyCompare(line.length(), 0):
+            return
+
+        # Draw the line itself
+        color = QColor()
+        color.setHsv(120 - 60 / self.steps_max * self.steps,
+                     180 + 50 / self.steps_max * self.steps,
+                     150 + 80 / self.steps_max * self.steps)
+        if self.highlighted:
+            color.setHsv(0, 0, 0)
+
+        style = self.line_style
+
+        painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin))
+        painter.drawLine(line)
+        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+
+        # Draw the arrows
+        angle = math.acos(line.dx() / line.length())
+        if line.dy() >= 0:
+            angle = (2.0 * math.pi) - angle
+
+        #  arrow in the middle of the arc
+        hpx = line.p1().x() + (line.dx() / 2.0)
+        hpy = line.p1().y() + (line.dy() / 2.0)
+        head_point = QPointF(hpx, hpy)
+
+        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+        destination_arrow_p1 = head_point + QPointF(
+            math.sin(angle - math.pi / 3) * self.arrow_size,
+            math.cos(angle - math.pi / 3) * self.arrow_size)
+        destination_arrow_p2 = head_point + QPointF(
+            math.sin(angle - math.pi + math.pi / 3) * self.arrow_size,
+            math.cos(angle - math.pi + math.pi / 3) * self.arrow_size)
+
+        painter.setBrush(color)
+        painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2]))
+
+        if self.metadata["confirmation_text"]:
+            painter.drawText(head_point, self.metadata["confirmation_text"])
+
+    def move_source_point(self, node_id, x, y):
+        """
+        Move to corresponding position
+        :param str node_id: the node id
+        :param float x: x coordinates
+        :param float y: y coordinates
+        :return:
+        """
+        if node_id == self.source:
+            self.source_point = QPointF(x, y)
+            self.update(self.boundingRect())
+
+    def move_destination_point(self, node_id, x, y):
+        """
+        Move to corresponding position
+        :param str node_id: the node id
+        :param float x: x coordinates
+        :param float y: y coordinates
+        :return:
+        """
+        if node_id == self.destination:
+            self.destination_point = QPointF(x, y)
+            self.update(self.boundingRect())
+
+    def highlight(self):
+        """
+        Highlight the edge in the scene
+        """
+        self.highlighted = True
+        self.update(self.boundingRect())
+
+    def neutralize(self):
+        """
+        Neutralize the edge in the scene
+        """
+        self.highlighted = False
+        self.update(self.boundingRect())
diff --git a/src/sakia/gui/views/arc.py b/src/sakia/gui/views/edges/wot_edge.py
similarity index 78%
rename from src/sakia/gui/views/arc.py
rename to src/sakia/gui/views/edges/wot_edge.py
index 6282581119a9076500df2ca39548765d01bdc3c9..50fcf150c949e89e3f2d468606fdc16c63eb9da7 100644
--- a/src/sakia/gui/views/arc.py
+++ b/src/sakia/gui/views/edges/wot_edge.py
@@ -1,13 +1,13 @@
-from PyQt5.QtWidgets import QGraphicsLineItem
 from PyQt5.QtCore import Qt, QRectF, QLineF, QPointF, QSizeF, \
                         qFuzzyCompare
 from PyQt5.QtGui import QColor, QPen, QPolygonF
 import math
-from ...core.graph.constants import ArcStatus
+from .base_edge import BaseEdge
+from ....core.graph.constants import EdgeStatus
 
 
-class Arc(QGraphicsLineItem):
-    def __init__(self, source_node, destination_node, metadata, pos, scale=1):
+class WotEdge(BaseEdge):
+    def __init__(self, source_node, destination_node, metadata, pos):
         """
         Create an arc between two nodes
 
@@ -15,23 +15,28 @@ class Arc(QGraphicsLineItem):
         :param Node destination_node: Destination node of the arc
         :param dict metadata: Arc metadata
         """
-        super(Arc, self).__init__()
-
-        self.metadata = metadata
-        self.source = source_node
-        self.destination = destination_node
-
-        self.status = self.metadata['status']
-
-        self.source_point = QPointF(pos[self.source][0], pos[self.source][1])*scale
-        self.destination_point = QPointF(pos[self.destination][0], pos[self.destination][1])*scale
-        self.arrow_size = 5.0
-
-        self.setAcceptedMouseButtons(Qt.NoButton)
+        super().__init__(source_node, destination_node, metadata, pos)
 
+        self.arrow_size = 5
         #  cursor change on hover
         self.setAcceptHoverEvents(True)
         self.setZValue(0)
+        self._colors = {
+            EdgeStatus.STRONG: 'blue',
+            EdgeStatus.WEAK: 'salmon'
+        }
+        self._line_styles = {
+            EdgeStatus.STRONG: Qt.SolidLine,
+            EdgeStatus.WEAK: Qt.DashLine
+        }
+
+    @property
+    def color_name(self):
+        return self._colors[self.status]
+
+    @property
+    def line_style(self):
+        return self._line_styles[self.status]
 
     # virtual function require subclassing
     def boundingRect(self):
@@ -73,12 +78,8 @@ class Arc(QGraphicsLineItem):
 
         # Draw the line itself
         color = QColor()
-        style = Qt.SolidLine
-        if self.status == ArcStatus.STRONG:
-            color.setNamedColor('blue')
-        if self.status == ArcStatus.WEAK:
-            color.setNamedColor('salmon')
-            style = Qt.DashLine
+        color.setNamedColor(self.color_name)
+        style = self.line_style
 
         painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin))
         painter.drawLine(line)
diff --git a/src/sakia/gui/views/explorer.py b/src/sakia/gui/views/explorer.py
new file mode 100644
index 0000000000000000000000000000000000000000..927d9f085cb63c2c633a878a0c70cb4f380781f1
--- /dev/null
+++ b/src/sakia/gui/views/explorer.py
@@ -0,0 +1,47 @@
+import logging
+
+import networkx
+from PyQt5.QtCore import Qt, QPoint, pyqtSignal
+from PyQt5.QtGui import QPainter, QWheelEvent
+from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene
+
+from .scenes import ExplorerScene
+
+
+class ExplorerView(QGraphicsView):
+    def __init__(self, parent=None):
+        """
+        Create View to display scene
+
+        :param parent:  [Optional, default=None] Parent widget
+        """
+        super().__init__(parent)
+
+        self.setScene(ExplorerScene(self))
+
+        self.setCacheMode(QGraphicsView.CacheBackground)
+        self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
+        self.setRenderHint(QPainter.Antialiasing)
+        self.setRenderHint(QPainter.SmoothPixmapTransform)
+
+    def wheelEvent(self, event: QWheelEvent):
+        """
+        Zoom in/out on the mouse cursor
+        """
+        # zoom only when CTRL key pressed
+        if (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier:
+            steps = event.angleDelta().y() / 15 / 8
+
+            if steps == 0:
+                event.ignore()
+                return
+
+            # scale factor 1.25
+            sc = pow(1.25, steps)
+            self.scale(sc, sc)
+            self.centerOn(self.mapToScene(event.pos()))
+            event.accept()
+        #  act normally on scrollbar
+        else:
+            # transmit event to parent class wheelevent
+            super(QGraphicsView, self).wheelEvent(event)
diff --git a/src/sakia/gui/views/nodes/__init__.py b/src/sakia/gui/views/nodes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..28332381ab8beea33a4eb493b6d21af09a582adb
--- /dev/null
+++ b/src/sakia/gui/views/nodes/__init__.py
@@ -0,0 +1,2 @@
+from .wot_node import WotNode
+from .explorer_node import ExplorerNode
\ No newline at end of file
diff --git a/src/sakia/gui/views/node.py b/src/sakia/gui/views/nodes/base_node.py
similarity index 67%
rename from src/sakia/gui/views/node.py
rename to src/sakia/gui/views/nodes/base_node.py
index 3c9944bfc6bd5d3850f30183bf7998fa2568125f..f486d1c556f02bb0d5876a8b953e0ebda91bd42e 100644
--- a/src/sakia/gui/views/node.py
+++ b/src/sakia/gui/views/nodes/base_node.py
@@ -1,14 +1,13 @@
 from PyQt5.QtWidgets import QGraphicsEllipseItem, \
-    QGraphicsSimpleTextItem, QMenu, QAction, QGraphicsSceneHoverEvent, \
+    QMenu, QAction, QGraphicsSceneHoverEvent, \
     QGraphicsSceneContextMenuEvent
+from PyQt5.QtCore import Qt, QCoreApplication, QT_TRANSLATE_NOOP, pyqtSignal
+from PyQt5.QtGui import QMouseEvent
+from sakia.core.graph.constants import NodeStatus
 
-from PyQt5.QtCore import Qt, QPointF, QCoreApplication, QT_TRANSLATE_NOOP
-from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QMouseEvent, QRadialGradient
-from ...core.graph.constants import NodeStatus
 
-
-class Node(QGraphicsEllipseItem):
-    def __init__(self, nx_node, pos, scale=1):
+class BaseNode(QGraphicsEllipseItem):
+    def __init__(self, nx_node, pos):
         """
         Create node in the graph scene
 
@@ -16,14 +15,13 @@ class Node(QGraphicsEllipseItem):
         :param x_y: Position of the node
         """
 
-        super(Node, self).__init__()
+        super().__init__()
 
         self.metadata = nx_node[1]
         self.id = nx_node[0]
         # unpack tuple
         x, y = pos[nx_node[0]]
-        x *= scale
-        y *= scale
+        self.setPos(x, y)
         self.status_wallet = self.metadata['status'] & NodeStatus.HIGHLIGHTED
         self.status_member = not self.metadata['status'] & NodeStatus.OUT
         self.text = self.metadata['text']
@@ -38,50 +36,6 @@ class Node(QGraphicsEllipseItem):
         self.action_contact = None
         self.action_show_member = None
 
-        # color around ellipse
-        outline_color = QColor('grey')
-        outline_style = Qt.SolidLine
-        outline_width = 1
-        if self.status_wallet:
-            outline_color = QColor('black')
-            outline_width = 2
-        if not self.status_member:
-            outline_color = QColor('red')
-            outline_style = Qt.SolidLine
-        self.setPen(QPen(outline_color, outline_width, outline_style))
-
-        # text inside ellipse
-        self.text_item = QGraphicsSimpleTextItem(self)
-        self.text_item.setText(self.text)
-        text_color = QColor('grey')
-        if self.status_wallet == NodeStatus.HIGHLIGHTED:
-            text_color = QColor('black')
-        self.text_item.setBrush(QBrush(text_color))
-        # center ellipse around text
-        self.setRect(
-            0,
-            0,
-            self.text_item.boundingRect().width() * 2,
-            self.text_item.boundingRect().height() * 2
-        )
-
-        #  set anchor to the center
-        self.setTransform(
-            QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0))
-        self.setPos(x, y)
-        # center text in ellipse
-        self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0)
-
-        # create gradient inside the ellipse
-        gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width())
-        gradient.setColorAt(0, QColor('white'))
-        gradient.setColorAt(1, QColor('darkgrey'))
-        self.setBrush(QBrush(gradient))
-
-        # cursor change on hover
-        self.setAcceptHoverEvents(True)
-        self.setZValue(1)
-
     def mousePressEvent(self, event: QMouseEvent):
         """
         Click on mouse button
@@ -99,6 +53,7 @@ class Node(QGraphicsEllipseItem):
         :param event: scene hover event
         """
         self.setCursor(Qt.ArrowCursor)
+        self.scene().node_hovered.emit(self.id)
 
     def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
         """
diff --git a/src/sakia/gui/views/nodes/explorer_node.py b/src/sakia/gui/views/nodes/explorer_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..bedc9b9df18957407021d3a5e1058122b7cfa3ac
--- /dev/null
+++ b/src/sakia/gui/views/nodes/explorer_node.py
@@ -0,0 +1,167 @@
+from PyQt5.QtWidgets import QGraphicsSimpleTextItem
+from PyQt5.QtCore import Qt, QPointF, QTimeLine, QTimer
+from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QRadialGradient
+from ....core.graph.constants import NodeStatus
+from .base_node import BaseNode
+import logging
+import math
+
+
+class ExplorerNode(BaseNode):
+    def __init__(self, nx_node, center_pos, nx_pos, steps, steps_max):
+        """
+        Create node in the graph scene
+
+        :param tuple nx_node: Node info
+        :param center_pos: The position of the center node
+        :param nx_pos: Position of the nodes in the graph
+        :param int steps: The steps from the center identity
+        :param int steps_max: The steps max of the graph
+        """
+        super().__init__(nx_node, nx_pos)
+
+        self.steps = steps
+        self.steps_max = steps_max
+        self.highlighted = False
+
+        # text inside ellipse
+        self.text_item = QGraphicsSimpleTextItem(self)
+        self.text_item.setText(self.text)
+        # center ellipse around text
+        self.setRect(
+            0,
+            0,
+            self.text_item.boundingRect().width() * 2,
+            self.text_item.boundingRect().height() * 2
+        )
+
+        #  set anchor to the center
+        self.setTransform(
+            QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0))
+        # center text in ellipse
+        self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0)
+
+        # cursor change on hover
+        self.setAcceptHoverEvents(True)
+        self.setZValue(1)
+
+        # animation and moves
+        self.timeline = None
+        self.loading_timer = QTimer()
+        self.loading_timer.timeout.connect(self.next_tick)
+        self.loading_counter = 0
+        self._refresh_colors()
+        self.setPos(center_pos)
+        self.move_to(nx_pos)
+
+    def _refresh_colors(self):
+        """
+        Refresh elements in the node
+        """
+        # color around ellipse
+        outline_color = QColor('black')
+        outline_style = Qt.SolidLine
+        outline_width = 1
+        if self.status_wallet:
+            outline_color = QColor('grey')
+            outline_width = 2
+        if not self.status_member:
+            outline_color = QColor('red')
+            outline_style = Qt.SolidLine
+        self.setPen(QPen(outline_color, outline_width, outline_style))
+
+        if self.highlighted:
+            text_color = QColor('grey')
+        else:
+            text_color = QColor('black')
+
+        if self.status_wallet == NodeStatus.HIGHLIGHTED:
+            text_color = QColor('grey')
+        self.text_item.setBrush(QBrush(text_color))
+
+        # create gradient inside the ellipse
+        gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width())
+        color = QColor()
+        color.setHsv(120 - 60 / self.steps_max * self.steps,
+                     180 + 50 / self.steps_max * self.steps,
+                     60 + 170 / self.steps_max * self.steps)
+        if self.highlighted:
+            color = color.darker(200)
+        color = color.lighter(math.fabs(math.sin(self.loading_counter / 100 * math.pi) * 100) + 100)
+        gradient.setColorAt(0, color)
+        gradient.setColorAt(1, color.darker(150))
+        self.setBrush(QBrush(gradient))
+
+    def move_to(self, nx_pos):
+        """
+        Move to corresponding position
+        :param nx_pos:
+        :return:
+        """
+        origin_x = self.x()
+        origin_y = self.y()
+        final_x = nx_pos[self.id][0]
+        final_y = nx_pos[self.id][1]
+
+        def frame_move(frame):
+            value = self.timeline.valueForTime(self.timeline.currentTime())
+            x = origin_x + (final_x - origin_x) * value
+            y = origin_y + (final_y - origin_y) * value
+            self.setPos(x, y)
+            self.scene().node_moved.emit(self.id, x, y)
+
+        def timeline_ends():
+            self.setPos(final_x, final_y)
+            self.timeline = None
+
+        # Remember to hold the references to QTimeLine and QGraphicsItemAnimation instances.
+        # They are not kept anywhere, even if you invoke QTimeLine.start().
+        self.timeline = QTimeLine(1000)
+        self.timeline.setFrameRange(0, 100)
+        self.timeline.frameChanged.connect(frame_move)
+        self.timeline.finished.connect(timeline_ends)
+
+        self.timeline.start()
+
+    def highlight(self):
+        """
+        Highlight the edge in the scene
+        """
+        self.highlighted = True
+        self._refresh_colors()
+        self.update(self.boundingRect())
+
+    def neutralize(self):
+        """
+        Neutralize the edge in the scene
+        """
+        self.highlighted = False
+        self._refresh_colors()
+        self.update(self.boundingRect())
+
+    def start_loading_animation(self):
+        """
+        Neutralize the edge in the scene
+        """
+        if not self.loading_timer.isActive():
+            self.loading_timer.start(10)
+
+    def stop_loading_animation(self):
+        """
+        Neutralize the edge in the scene
+        """
+        self.loading_timer.stop()
+        self.loading_counter = 100
+        self._refresh_colors()
+        self.update(self.boundingRect())
+
+    def next_tick(self):
+        """
+        Next tick
+        :return:
+        """
+        self.loading_counter += 1
+        self.loading_counter %= 100
+        self._refresh_colors()
+        self.update(self.boundingRect())
+
diff --git a/src/sakia/gui/views/nodes/wot_node.py b/src/sakia/gui/views/nodes/wot_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef7ba7dc1e0c43b631b05c28d032dbf0228cd646
--- /dev/null
+++ b/src/sakia/gui/views/nodes/wot_node.py
@@ -0,0 +1,59 @@
+from PyQt5.QtWidgets import QGraphicsSimpleTextItem
+from PyQt5.QtCore import Qt, QPointF
+from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QRadialGradient
+from ....core.graph.constants import NodeStatus
+from .base_node import BaseNode
+
+
+class WotNode(BaseNode):
+    def __init__(self, nx_node, pos):
+        """
+        Create node in the graph scene
+
+        :param tuple nx_node: Node info
+        :param x_y: Position of the node
+        """
+        super().__init__(nx_node, pos)
+
+        # color around ellipse
+        outline_color = QColor('grey')
+        outline_style = Qt.SolidLine
+        outline_width = 1
+        if self.status_wallet:
+            outline_color = QColor('black')
+            outline_width = 2
+        if not self.status_member:
+            outline_color = QColor('red')
+            outline_style = Qt.SolidLine
+        self.setPen(QPen(outline_color, outline_width, outline_style))
+
+        # text inside ellipse
+        self.text_item = QGraphicsSimpleTextItem(self)
+        self.text_item.setText(self.text)
+        text_color = QColor('grey')
+        if self.status_wallet == NodeStatus.HIGHLIGHTED:
+            text_color = QColor('black')
+        self.text_item.setBrush(QBrush(text_color))
+        # center ellipse around text
+        self.setRect(
+            0,
+            0,
+            self.text_item.boundingRect().width() * 2,
+            self.text_item.boundingRect().height() * 2
+        )
+
+        #  set anchor to the center
+        self.setTransform(
+            QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0))
+        # center text in ellipse
+        self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0)
+
+        # create gradient inside the ellipse
+        gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width())
+        gradient.setColorAt(0, QColor('white'))
+        gradient.setColorAt(1, QColor('darkgrey'))
+        self.setBrush(QBrush(gradient))
+
+        # cursor change on hover
+        self.setAcceptHoverEvents(True)
+        self.setZValue(1)
diff --git a/src/sakia/gui/views/scenes/__init__.py b/src/sakia/gui/views/scenes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2eef13ee6a02e87b65491aa2d076785a7fc84d1
--- /dev/null
+++ b/src/sakia/gui/views/scenes/__init__.py
@@ -0,0 +1,2 @@
+from .wot_scene import WotScene
+from .explorer_scene import ExplorerScene
\ No newline at end of file
diff --git a/src/sakia/gui/views/scenes/base_scene.py b/src/sakia/gui/views/scenes/base_scene.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4762b0c4926d4d9fc473c06b840c789828c3c6
--- /dev/null
+++ b/src/sakia/gui/views/scenes/base_scene.py
@@ -0,0 +1,16 @@
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtWidgets import QGraphicsScene
+
+
+class BaseScene(QGraphicsScene):
+    # This defines signals taking string arguments
+    node_clicked = pyqtSignal(str, dict)
+    node_signed = pyqtSignal(str, dict)
+    node_transaction = pyqtSignal(str, dict)
+    node_contact = pyqtSignal(str, dict)
+    node_member = pyqtSignal(str, dict)
+    node_copy_pubkey = pyqtSignal(str)
+    node_hovered = pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
\ No newline at end of file
diff --git a/src/sakia/gui/views/scenes/explorer_scene.py b/src/sakia/gui/views/scenes/explorer_scene.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c069ead7b04956cccec6cfa80f19c43715a0bab
--- /dev/null
+++ b/src/sakia/gui/views/scenes/explorer_scene.py
@@ -0,0 +1,300 @@
+import networkx
+import logging
+import math
+from PyQt5.QtCore import QPoint, pyqtSignal
+from PyQt5.QtWidgets import QGraphicsScene
+
+from ..edges import ExplorerEdge
+from ..nodes import ExplorerNode
+
+from .base_scene import BaseScene
+
+
+class ExplorerScene(BaseScene):
+
+    node_moved = pyqtSignal(str, float, float)
+
+    def __init__(self, parent=None):
+        """
+        Create scene of the graph
+
+        :param parent: [Optional, default=None] Parent view
+        """
+        super().__init__(parent)
+
+        self.lastDragPos = QPoint()
+        self.setItemIndexMethod(QGraphicsScene.NoIndex)
+
+        # list of nodes in scene
+        self.nodes = dict()
+        #  axis of the scene for debug purpose
+        # self.addLine(-100, 0, 100, 0)
+        # self.addLine(0, -100, 0, 100)
+        self.node_hovered.connect(self.display_path_to)
+
+        # list of nodes in scene
+        self.nodes = dict()
+        self.edges = dict()
+        self.busy = None
+        self.nx_graph = None
+        self.identity = None
+        #  axis of the scene for debug purpose
+        # self.addLine(-100, 0, 100, 0)
+        # self.addLine(0, -100, 0, 100)
+
+    @staticmethod
+    def _init_layout(nx_graph):
+        """
+        Init the data of the layout
+        :param MultiGraph nx_graph:
+        """
+        data = {}
+        INF = len(nx_graph.nodes()) * len(nx_graph.nodes())
+
+        for node in nx_graph.nodes():
+            data[node] = {
+                'theta': None,
+                'scenter': INF,
+                'nchild': 0,
+                'sparent': None,
+                'stsize': 0.0,
+                'span': 0.0
+            }
+        return data
+
+    @staticmethod
+    def _set_nstep_to_center(nx_graph, data, current):
+        """
+        Set the number of steps to the center
+        :param networkx.MultiGraph nx_graph: the graph
+        :param dict data: the data of the layout
+        """
+        queue = [current]
+        while queue:
+            n = queue.pop()
+            nsteps = data[n]['scenter'] + 1
+            for edge in networkx.edges(nx_graph, n):
+                next_node = edge[0] if edge[0] is not n else edge[1]
+                if data[next_node]['sparent']:
+                    continue
+                if nsteps < data[next_node]['scenter']:
+                    data[next_node]['scenter'] = nsteps
+                    data[next_node]['sparent'] = n
+                    data[n]['nchild'] += 1
+                    queue.append(next_node)
+
+    @staticmethod
+    def _set_parent_nodes(nx_graph, data, center):
+        """
+        Set the parent of each node
+        :param networkx.MultiGraph nx_graph: the graph
+        :param dict data: the data of the layout
+        :param str center: the id of the node at the center
+        """
+        unset = data[center]['scenter']
+        data[center]['scenter'] = 0
+        data[center]['sparent'] = None
+
+        logging.debug("Parent node of {0}".format(center))
+        ExplorerScene._set_nstep_to_center(nx_graph, data, center)
+        for node in nx_graph.nodes():
+            if data[node]['scenter'] == unset:
+                return -1
+        return max([n['scenter'] for n in data.values()])
+
+    @staticmethod
+    def _set_subtree_size(nx_graph, data):
+        """
+        Compute the subtree size of each node, which is the
+        number of leaves in subtree rooted to the node
+        :param networkx.MultiGraph nx_graph: the graph
+        :param dict data:
+        """
+        for node in nx_graph.nodes():
+            if data[node]['nchild'] > 0:
+                continue
+            data[node]['stsize'] += 1
+            parent = data[node]['sparent']
+            while parent:
+                data[parent]['stsize'] += 1
+                parent = data[parent]['sparent']
+
+    @staticmethod
+    def _set_subtree_spans(nx_graph, data, current):
+        """
+        Compute the subtree spans of each node
+        :param networkx.MultiGraph nx_graph: the graph
+        :param dict data: the data of the layout
+        :param str current: the current node which we compute the subtree
+        """
+        ratio = data[current]['span'] / data[current]['stsize']
+        for edge in nx_graph.edges(current):
+            next_node = edge[0] if edge[0] != current else edge[1]
+            if data[next_node]['sparent'] != current:
+                continue
+            if data[next_node]['span'] != 0.0:
+                continue
+
+            data[next_node]['span'] = ratio * data[next_node]['stsize']
+            if data[next_node]['nchild'] > 0:
+                ExplorerScene._set_subtree_spans(nx_graph, data, next_node)
+
+    @staticmethod
+    def _set_positions(nx_graph, data, current):
+        """
+        Compute the polar positions of each node
+        :param networkx.MultiDiGraph nx_graph: the graph
+        :param dict data: the data of the layout
+        :param str current: the current node which we compute the subtree
+        """
+        if not data[current]['sparent']:
+            theta = 0
+        else:
+            theta = data[current]['theta'] - data[current]['span'] / 2
+
+        for edge in nx_graph.edges(current):
+            next_node = edge[0] if edge[0] != current else edge[1]
+            if data[next_node]['sparent'] != current:
+                continue
+            if data[next_node]['theta']:
+                continue
+
+            data[next_node]['theta'] = theta + data[next_node]['span'] / 2.0
+            theta += data[next_node]['span']
+            if data[next_node]['nchild'] > 0:
+                ExplorerScene._set_positions(nx_graph, data, next_node)
+
+    @staticmethod
+    def twopi_layout(nx_graph, center=None):
+        """
+        Render the twopi layout. Ported from C code available at
+        https://github.com/ellson/graphviz/blob/master/lib/twopigen/circle.c
+
+        :param networkx.MultiDiGraph nx_graph: the networkx graph
+        :param str center: the centered node
+        :return:
+        """
+        if len(nx_graph.nodes()) == 0:
+            return {}
+
+        if len(nx_graph.nodes()) == 1:
+            return {nx_graph.nodes()[0]: (0, 0)}
+        nx_graph = nx_graph.to_undirected()
+
+        data = ExplorerScene._init_layout(nx_graph)
+        if not center:
+            center = networkx.center(nx_graph)[0]
+        ExplorerScene._set_parent_nodes(nx_graph, data, center)
+        ExplorerScene._set_subtree_size(nx_graph, data)
+        data[center]['span'] = 2 * math.pi
+        ExplorerScene._set_subtree_spans(nx_graph, data, center)
+        data[center]['theta'] = 0.0
+        ExplorerScene._set_positions(nx_graph, data, center)
+
+        distances = networkx.shortest_path_length(nx_graph, center)
+        nx_pos = {}
+        for node in nx_graph.nodes():
+            hyp = distances[node] + 1
+            theta = data[node]['theta']
+            nx_pos[node] = (hyp * math.cos(theta) * 100, hyp * math.sin(theta) * 100)
+        return nx_pos
+
+    def clear(self):
+        """
+        clear the scene
+        """
+        for node in self.nodes.values():
+            if node.timeline:
+                node.timeline.stop()
+
+        self.nodes.clear()
+        self.edges.clear()
+        super().clear()
+
+    def update_current_identity(self, identity_pubkey):
+        """
+        Update the current identity loaded
+
+        :param str identity_pubkey:
+        """
+        for node in self.nodes.values():
+            node.stop_loading_animation()
+
+        if identity_pubkey in self.nodes:
+            self.nodes[identity_pubkey].start_loading_animation()
+
+    def update_wot(self, nx_graph, identity, dist_max):
+        """
+        draw community graph
+
+        :param networkx.Graph nx_graph: graph to draw
+        :param sakia.core.registry.Identity identity: the wot of the identity
+        :param dist_max: the dist_max to display
+        """
+        #  clear scene
+        self.identity = identity
+        self.nx_graph = nx_graph.copy()
+
+        graph_pos = ExplorerScene.twopi_layout(nx_graph, center=identity.pubkey)
+        if len(nx_graph.nodes()) > 0:
+            distances = networkx.shortest_path_length(nx_graph.to_undirected(), identity.pubkey)
+        else:
+            distances = {}
+
+        # create networkx graph
+        for nx_node in nx_graph.nodes(data=True):
+            if nx_node[0] in self.nodes:
+                v = self.nodes[nx_node[0]]
+                v.move_to(graph_pos)
+            else:
+                center_pos = None
+                if len(nx_graph.edges(nx_node[0])) > 0:
+                    for edge in nx_graph.edges(nx_node[0]):
+                        neighbour = edge[0] if edge[0] != nx_node[0] else edge[1]
+                        if neighbour in self.nodes:
+                            center_pos = self.nodes[neighbour].pos()
+                            break
+                if not center_pos:
+                    if identity.pubkey in self.nodes:
+                        center_pos = self.nodes[identity.pubkey].pos()
+                    else:
+                        center_pos = QPoint(0, 0)
+
+                v = ExplorerNode(nx_node, center_pos, graph_pos, distances[nx_node[0]], dist_max)
+                self.addItem(v)
+                self.nodes[nx_node[0]] = v
+
+        for edge in nx_graph.edges(data=True):
+            edge[2]["confirmation_text"] = ""
+            if (edge[0], edge[1]) not in self.edges and (edge[1], edge[0]) not in self.edges:
+                distance = max(self.nodes[edge[0]].steps, self.nodes[edge[1]].steps)
+                explorer_edge = ExplorerEdge(edge[0], edge[1], edge[2], graph_pos, distance, dist_max)
+                self.node_moved.connect(explorer_edge.move_source_point)
+                self.node_moved.connect(explorer_edge.move_destination_point)
+                self.addItem(explorer_edge)
+                self.edges[(edge[0], edge[1])] = explorer_edge
+
+        self.update()
+
+    def display_path_to(self, node_id):
+        if node_id != self.identity.pubkey:
+            for edge in self.edges.values():
+                edge.neutralize()
+
+            for node in self.nodes.values():
+                node.neutralize()
+            try:
+                path = networkx.shortest_path(self.nx_graph.to_undirected(), self.identity.pubkey, node_id)
+
+                for node, next_node in zip(path[:-1], path[1:]):
+                    if (node, next_node) in self.edges:
+                        edge = self.edges[(node, next_node)]
+                    elif (next_node, node) in self.edges:
+                        edge = self.edges[(next_node, node)]
+                    if edge:
+                        edge.highlight()
+                        self.nodes[node].highlight()
+                        self.nodes[next_node].highlight()
+                        logging.debug("Update edge between {0} and {1}".format(node, next_node))
+            except (networkx.exception.NetworkXError, networkx.exception.NetworkXNoPath) as e:
+                logging.debug(str(e))
diff --git a/src/sakia/gui/views/scenes/wot_scene.py b/src/sakia/gui/views/scenes/wot_scene.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1e187a17a8938e4cfb226d577f3b7ec39cbbfd6
--- /dev/null
+++ b/src/sakia/gui/views/scenes/wot_scene.py
@@ -0,0 +1,171 @@
+import networkx
+from PyQt5.QtCore import QPoint, pyqtSignal
+from PyQt5.QtWidgets import QGraphicsScene
+
+from sakia.gui.views.edges import WotEdge
+from sakia.gui.views.nodes import WotNode
+
+from .base_scene import BaseScene
+
+
+class WotScene(BaseScene):
+    def __init__(self, parent=None):
+        """
+        Create scene of the graph
+
+        :param parent: [Optional, default=None] Parent view
+        """
+        super().__init__(parent)
+
+        self.lastDragPos = QPoint()
+        self.setItemIndexMethod(QGraphicsScene.NoIndex)
+
+        # list of nodes in scene
+        self.nodes = dict()
+        #  axis of the scene for debug purpose
+        # self.addLine(-100, 0, 100, 0)
+        # self.addLine(0, -100, 0, 100)
+
+    @staticmethod
+    def certified_partial_layout(nx_graph, center, scale=1):
+        """
+        Method to generate a partial wot with certifiers layout
+        :param networkx.MultiDiGraph nx_graph: graph of the wot
+        :param center: the centered node
+        :param scale: a scale
+        :return: the positions of the nodes
+        """
+        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
+        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
+
+        certified = [n for n in nx_graph.nodes(data=True) if n[0] in certified_edge]
+
+        pos = {center: (0, max(len(certified_edge),
+                            len(certifier_edge))/2*0.12*scale)}
+
+        y = 0
+        x = 1 * scale
+        # sort by text
+        sort_certified = sorted(certified, key=lambda node_: node_[1]['text'].lower())
+        # add nodes and arcs
+        for n in sort_certified:
+            y += 0.25 * scale
+            pos[n[0]] = (x, y)
+        return pos
+
+    @staticmethod
+    def certifiers_partial_layout(nx_graph, center, scale=1):
+        """
+        Method to generate a partial wot with certifiers layout
+        :param networkx.MultiDiGraph nx_graph: graph of the wot
+        :param center: the centered node
+        :param scale: a scale
+        :return: the positions of the nodes
+        """
+        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
+        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
+
+        certifier = [n for n in nx_graph.nodes(data=True) if n[0] in certifier_edge]
+
+        pos = {center: (0, max(len(certified_edge),
+                                    len(certifier_edge))/2*0.12*scale)}
+
+        y = 0
+        x = -1 * scale
+        # sort by text
+        sort_certifier = sorted(certifier, key=lambda node_: node_[1]['text'].lower())
+        # add nodes and arcs
+        for n in sort_certifier:
+            y += 0.25 * scale
+            pos[n[0]] = (x, y)
+
+        return pos
+
+    @staticmethod
+    def certified_partial_layout(nx_graph, center, scale=1):
+        """
+        Method to generate a partial wot with certifiers layout
+        :param networkx.MultiDiGraph nx_graph: graph of the wot
+        :param center: the centered node
+        :param scale: a scale
+        :return: the positions of the nodes
+        """
+        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
+        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
+
+        certified = [n for n in nx_graph.nodes(data=True) if n[0] in certified_edge]
+
+        pos = {center: (0, max(len(certified_edge),
+                            len(certifier_edge))/2*0.12*scale)}
+
+        y = 0
+        x = 1 * scale
+        # sort by text
+        sort_certified = sorted(certified, key=lambda node_: node_[1]['text'].lower())
+        # add nodes and arcs
+        for n in sort_certified:
+            y += 0.25 * scale
+            pos[n[0]] = (x, y)
+        return pos
+
+    @staticmethod
+    def path_partial_layout(nx_graph, path, scale=1):
+        """
+
+        :param networkx.MultiDiGraph nx_graph: The graph to show
+        :param list path:
+        :param int scale:
+        :return:
+        """
+        destination = path[-1]
+        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == destination]
+        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == destination]
+
+        x = 0
+        y = max(len(certified_edge), len(certifier_edge))/2*0.12*scale
+        pos = {destination: (x, y)}
+
+        for node in reversed(path[:-1]):
+            y -= 100
+            pos[node] = (x, y)
+        return pos
+
+    def update_wot(self, nx_graph, identity):
+        """
+        draw community graph
+
+        :param networkx.MultiDiGraph nx_graph: graph to draw
+        :param sakia.core.registry.Identity identity: the wot of the identity
+        """
+        #  clear scene
+        self.clear()
+        certifiers_graph_pos = WotScene.certifiers_partial_layout(nx_graph, identity.pubkey, scale=200)
+        certified_graph_pos = WotScene.certified_partial_layout(nx_graph, identity.pubkey, scale=200)
+
+        # create networkx graph
+        for node in nx_graph.nodes(data=True):
+            if node[0] in certifiers_graph_pos:
+                v = WotNode(node, certifiers_graph_pos)
+                self.addItem(v)
+            if node[0] in certified_graph_pos:
+                v = WotNode(node, certified_graph_pos)
+                self.addItem(v)
+
+        for edge in nx_graph.edges(data=True):
+            if edge[0] in certifiers_graph_pos and edge[1] == identity.pubkey:
+                self.addItem(WotEdge(edge[0], edge[1], edge[2], certifiers_graph_pos))
+            if edge[0] == identity.pubkey and edge[1] in certified_graph_pos:
+                self.addItem(WotEdge(edge[0], edge[1], edge[2], certified_graph_pos))
+
+        self.update()
+
+    def update_path(self, nx_graph, path):
+        path_graph_pos = WotScene.path_partial_layout(nx_graph, path, scale=200)
+        nodes_path = [n for n in nx_graph.nodes(data=True) if n[0] in path[:-1]]
+        for node in nodes_path:
+            v = WotNode(node, path_graph_pos)
+            self.addItem(v)
+
+        for edge in nx_graph.edges(data=True):
+            if edge[0] in path_graph_pos and edge[1] in path_graph_pos:
+                self.addItem(WotEdge(edge[0], edge[1], edge[2], path_graph_pos))
diff --git a/src/sakia/gui/views/wot.py b/src/sakia/gui/views/wot.py
index b17b978cf88230b4767c12b25334c7e44a257036..c4e874e971f68cad026eacfe17d819c1c3d657a5 100644
--- a/src/sakia/gui/views/wot.py
+++ b/src/sakia/gui/views/wot.py
@@ -1,10 +1,12 @@
 import networkx
-from PyQt5.QtGui import QPainter, QWheelEvent
 from PyQt5.QtCore import Qt, QPoint,  pyqtSignal
+from PyQt5.QtGui import QPainter, QWheelEvent
 from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene
 
-from .node import Node
-from .arc import Arc
+from .edges import WotEdge
+from .nodes import WotNode
+from .scenes import WotScene
+
 
 class WotView(QGraphicsView):
     def __init__(self, parent=None):
@@ -15,7 +17,7 @@ class WotView(QGraphicsView):
         """
         super(WotView, self).__init__(parent)
 
-        self.setScene(Scene(self))
+        self.setScene(WotScene(self))
 
         self.setCacheMode(QGraphicsView.CacheBackground)
         self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
@@ -42,171 +44,4 @@ class WotView(QGraphicsView):
         #  act normally on scrollbar
         else:
             # transmit event to parent class wheelevent
-            super(QGraphicsView, self).wheelEvent(event)
-
-
-class Scene(QGraphicsScene):
-    # This defines signals taking string arguments
-    node_clicked = pyqtSignal(str, dict, name='nodeClicked')
-    node_signed = pyqtSignal(str, dict, name='nodeSigned')
-    node_transaction = pyqtSignal(str, dict, name='nodeTransaction')
-    node_contact = pyqtSignal(str, dict, name='nodeContact')
-    node_member = pyqtSignal(str, dict, name='nodeMember')
-    node_copy_pubkey = pyqtSignal(str, name='nodeCopyPubkey')
-
-    def __init__(self, parent=None):
-        """
-        Create scene of the graph
-
-        :param parent: [Optional, default=None] Parent view
-        """
-        super(Scene, self).__init__(parent)
-
-        self.lastDragPos = QPoint()
-        self.setItemIndexMethod(QGraphicsScene.NoIndex)
-
-        # list of nodes in scene
-        self.nodes = dict()
-        #  axis of the scene for debug purpose
-        # self.addLine(-100, 0, 100, 0)
-        # self.addLine(0, -100, 0, 100)
-
-    def certified_partial_layout(self, nx_graph, center, scale=1):
-        """
-        Method to generate a partial wot with certifiers layout
-        :param networkx.MultiDiGraph nx_graph: graph of the wot
-        :param center: the centered node
-        :param scale: a scale
-        :return: the positions of the nodes
-        """
-        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
-        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
-
-        certified = [n for n in nx_graph.nodes(data=True) if n[0] in certified_edge]
-
-        pos = {center: (0, max(len(certified_edge),
-                            len(certifier_edge))/2*0.12*scale)}
-
-        y = 0
-        x = 1 * scale
-        # sort by text
-        sort_certified = sorted(certified, key=lambda node_: node_[1]['text'].lower())
-        # add nodes and arcs
-        for n in sort_certified:
-            y += 0.25 * scale
-            pos[n[0]] = (x, y)
-        return pos
-
-    def certifiers_partial_layout(self, nx_graph, center, scale=1):
-        """
-        Method to generate a partial wot with certifiers layout
-        :param networkx.MultiDiGraph nx_graph: graph of the wot
-        :param center: the centered node
-        :param scale: a scale
-        :return: the positions of the nodes
-        """
-        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
-        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
-
-        certifier = [n for n in nx_graph.nodes(data=True) if n[0] in certifier_edge]
-
-        pos = {center: (0, max(len(certified_edge),
-                                    len(certifier_edge))/2*0.12*scale)}
-
-        y = 0
-        x = -1 * scale
-        # sort by text
-        sort_certifier = sorted(certifier, key=lambda node_: node_[1]['text'].lower())
-        # add nodes and arcs
-        for n in sort_certifier:
-            y += 0.25 * scale
-            pos[n[0]] = (x, y)
-
-        return pos
-
-    def certified_partial_layout(self, nx_graph, center, scale=1):
-        """
-        Method to generate a partial wot with certifiers layout
-        :param networkx.MultiDiGraph nx_graph: graph of the wot
-        :param center: the centered node
-        :param scale: a scale
-        :return: the positions of the nodes
-        """
-        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == center]
-        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == center]
-
-        certified = [n for n in nx_graph.nodes(data=True) if n[0] in certified_edge]
-
-        pos = {center: (0, max(len(certified_edge),
-                            len(certifier_edge))/2*0.12*scale)}
-
-        y = 0
-        x = 1 * scale
-        # sort by text
-        sort_certified = sorted(certified, key=lambda node_: node_[1]['text'].lower())
-        # add nodes and arcs
-        for n in sort_certified:
-            y += 0.25 * scale
-            pos[n[0]] = (x, y)
-        return pos
-
-    def path_partial_layout(self, nx_graph, path, scale=1):
-        """
-
-        :param networkx.Graph nx_graph: The graph to show
-        :param list path:
-        :param int scale:
-        :return:
-        """
-        destination = path[-1]
-        certifier_edge = [edge[0] for edge in nx_graph.in_edges() if edge[1] == destination]
-        certified_edge = [edge[1] for edge in nx_graph.out_edges() if edge[0] == destination]
-
-        x = 0
-        y = max(len(certified_edge), len(certifier_edge))/2*0.12*scale
-        pos = {destination: (x, y)}
-
-        for node in reversed(path[:-1]):
-            y -= 100
-            pos[node] = (x, y)
-        return pos
-
-    def update_wot(self, nx_graph, identity):
-        """
-        draw community graph
-
-        :param networkx.Graph nx_graph: graph to draw
-        :param sakia.core.registry.Identity identity: the wot of the identity
-        """
-        #  clear scene
-        self.clear()
-        certifiers_graph_pos = self.certifiers_partial_layout(nx_graph, identity.pubkey, scale=200)
-        certified_graph_pos = self.certified_partial_layout(nx_graph, identity.pubkey, scale=200)
-
-        # create networkx graph
-        for node in nx_graph.nodes(data=True):
-            if node[0] in certifiers_graph_pos:
-                v = Node(node, certifiers_graph_pos)
-                self.addItem(v)
-            if node[0] in certified_graph_pos:
-                v = Node(node, certified_graph_pos)
-                self.addItem(v)
-
-        for edge in nx_graph.edges(data=True):
-            if edge[0] in certifiers_graph_pos and edge[1] == identity.pubkey:
-                self.addItem(Arc(edge[0], edge[1], edge[2], certifiers_graph_pos))
-            if edge[0] == identity.pubkey and edge[1] in certified_graph_pos:
-                self.addItem(Arc(edge[0], edge[1], edge[2], certified_graph_pos))
-
-        self.update()
-
-    def update_path(self, nx_graph, path):
-        path_graph_pos = self.path_partial_layout(nx_graph, path, scale=200)
-        nodes_path = [n for n in nx_graph.nodes(data=True) if n[0] in path[:-1]]
-        for node in nodes_path:
-            v = Node(node, path_graph_pos)
-            self.addItem(v)
-
-        for edge in nx_graph.edges(data=True):
-            if edge[0] in path_graph_pos and edge[1] in path_graph_pos:
-                self.addItem(Arc(edge[0], edge[1], edge[2], path_graph_pos))
+            super().wheelEvent(event)
diff --git a/src/sakia/gui/widgets/search_user.py b/src/sakia/gui/widgets/search_user.py
new file mode 100644
index 0000000000000000000000000000000000000000..94114755631209cefe8ea8551beecb72cc4d0a59
--- /dev/null
+++ b/src/sakia/gui/widgets/search_user.py
@@ -0,0 +1,106 @@
+import logging
+
+from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP
+from PyQt5.QtWidgets import QComboBox, QWidget
+
+from ucoinpy.api import bma
+
+from ...tools.decorators import asyncify
+from ...tools.exceptions import NoPeerAvailable
+from ...core.registry import BlockchainState, Identity
+from ...gen_resources.search_user_view_uic import Ui_SearchUserWidget
+
+
+class SearchUserWidget(QWidget, Ui_SearchUserWidget):
+    _search_placeholder = QT_TRANSLATE_NOOP("SearchUserWidget", "Research a pubkey, an uid...")
+
+    identity_selected = pyqtSignal(Identity)
+    reset = pyqtSignal()
+
+    def __init__(self, parent):
+        """
+        :param sakia.core.app.Application app: Application instance
+        """
+        # construct from qtDesigner
+        super().__init__(parent)
+        self.setupUi(self)
+        # Default text when combo lineEdit is empty
+        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserWidget._search_placeholder))
+        #  add combobox events
+        self.combobox_search.lineEdit().returnPressed.connect(self.search)
+        # To fix a recall of the same item with different case,
+        # the edited text is not added in the item list
+        self.combobox_search.setInsertPolicy(QComboBox.NoInsert)
+        self.combobox_search.activated.connect(self.select_node)
+        self.button_reset.clicked.connect(self.reset)
+        self.nodes = list()
+        self.community = None
+        self.account = None
+        self.app = None
+
+    def init(self, app):
+        """
+        Initialize the widget
+        :param sakia.core.Application app: the application
+        """
+        self.app = app
+
+    def change_account(self, account):
+        self.account = account
+
+    def change_community(self, community):
+        self.community = community
+
+    @asyncify
+    async def search(self):
+        """
+        Search nodes when return is pressed in combobox lineEdit
+        """
+        text = self.combobox_search.lineEdit().text()
+
+        if len(text) < 2:
+            return False
+        try:
+            response = await self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})
+
+            nodes = {}
+            for identity in response['results']:
+                nodes[identity['pubkey']] = identity['uids'][0]['uid']
+
+            if nodes:
+                self.nodes = list()
+                self.blockSignals(True)
+                self.combobox_search.clear()
+                self.combobox_search.lineEdit().setText(text)
+                for pubkey, uid in nodes.items():
+                    self.nodes.append({'pubkey': pubkey, 'uid': uid})
+                    self.combobox_search.addItem(uid)
+                self.blockSignals(False)
+                self.combobox_search.showPopup()
+        except NoPeerAvailable:
+            pass
+
+    def select_node(self, index):
+        """
+        Select node in graph when item is selected in combobox
+        """
+        if index < 0 or index >= len(self.nodes):
+            return False
+        node = self.nodes[index]
+        metadata = {'id': node['pubkey'], 'text': node['uid']}
+        self.identity_selected.emit(
+            self.app.identities_registry.from_handled_data(
+                metadata['text'],
+                metadata['id'],
+                None,
+                BlockchainState.VALIDATED,
+                self.community
+            )
+        )
+
+    def retranslateUi(self, widget):
+        """
+        Retranslate missing widgets from generated code
+        """
+        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserWidget._search_placeholder))
+        super().retranslateUi(self)
diff --git a/src/sakia/tests/unit/core/graph/test_base_graph.py b/src/sakia/tests/unit/core/graph/test_base_graph.py
index b06c202b4c521b3b0aed7019d8cd1ddfb2fec5c5..7446f71f75ea0e8e8ba984dae1fd0ff5b6f38b32 100644
--- a/src/sakia/tests/unit/core/graph/test_base_graph.py
+++ b/src/sakia/tests/unit/core/graph/test_base_graph.py
@@ -5,7 +5,7 @@ from asynctest.mock import Mock, CoroutineMock, patch
 from PyQt5.QtCore import QLocale
 from sakia.tests import QuamashTest
 from sakia.core.graph import BaseGraph
-from sakia.core.graph.constants import ArcStatus, NodeStatus
+from sakia.core.graph.constants import EdgeStatus, NodeStatus
 
 
 class TestBaseGraph(unittest.TestCase, QuamashTest):
@@ -40,9 +40,9 @@ class TestBaseGraph(unittest.TestCase, QuamashTest):
         base_graph = BaseGraph(app, community)
 
         async def exec_test():
-            self.assertEquals((await base_graph.arc_status(48000)), ArcStatus.WEAK)
-            self.assertEquals((await base_graph.arc_status(49500)), ArcStatus.STRONG)
-            self.assertEquals((await base_graph.arc_status(49200)), ArcStatus.WEAK)
+            self.assertEquals((await base_graph.arc_status(48000)), EdgeStatus.WEAK)
+            self.assertEquals((await base_graph.arc_status(49500)), EdgeStatus.STRONG)
+            self.assertEquals((await base_graph.arc_status(49200)), EdgeStatus.WEAK)
 
         self.lp.run_until_complete(exec_test())
 
@@ -135,12 +135,12 @@ class TestBaseGraph(unittest.TestCase, QuamashTest):
 
             arc_from_first = [e for e in edges if e[0] == self.first_identity.pubkey][0]
             self.assertEqual(arc_from_first[1], self.account_identity.pubkey)
-            self.assertEqual(arc_from_first[2]['status'], ArcStatus.WEAK)
+            self.assertEqual(arc_from_first[2]['status'], EdgeStatus.WEAK)
             self.assertEqual(arc_from_first[2]['cert_time'], certifications[0]['cert_time'])
 
             arc_from_second = [e for e in edges if e[0] == self.second_identity.pubkey][0]
             self.assertEqual(arc_from_second[1], self.account_identity.pubkey)
-            self.assertEqual(arc_from_second[2]['status'], ArcStatus.STRONG)
+            self.assertEqual(arc_from_second[2]['status'], EdgeStatus.STRONG)
             self.assertEqual(arc_from_second[2]['cert_time'], certifications[1]['cert_time'])
 
         self.lp.run_until_complete(exec_test())
@@ -200,8 +200,3 @@ class TestBaseGraph(unittest.TestCase, QuamashTest):
         self.assertEqual(account_node[1]['status'], NodeStatus.HIGHLIGHTED)
         self.assertEqual(account_node[1]['text'], self.account_identity.uid)
         self.assertEqual(account_node[1]['tooltip'], self.account_identity.pubkey)
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/graph/test_explorer_graph.py b/src/sakia/tests/unit/core/graph/test_explorer_graph.py
index 457e4fed6c76b60fbd87a2da7c225f29c99f4316..035faab3b06da16e604c48779e56c0a8882ad295 100644
--- a/src/sakia/tests/unit/core/graph/test_explorer_graph.py
+++ b/src/sakia/tests/unit/core/graph/test_explorer_graph.py
@@ -72,7 +72,7 @@ class TestExplorerGraph(unittest.TestCase, QuamashTest):
                                                                }
                                                            ])
 
-        self.idC.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certifierd_by',
+        self.idC.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
                                                            return_value=[
                                                                {
                                                                    'cert_time': 49100,
@@ -123,7 +123,7 @@ class TestExplorerGraph(unittest.TestCase, QuamashTest):
     @patch('sakia.core.Application')
     @patch('sakia.core.Community')
     @patch('time.time', Mock(return_value=50000))
-    def test_explore_full(self, app, community):
+    def test_explore_full_from_center(self, app, community):
         community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
         community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
         app.preferences = {'expert_mode': True}
@@ -137,6 +137,23 @@ class TestExplorerGraph(unittest.TestCase, QuamashTest):
 
         self.lp.run_until_complete(exec_test())
 
+    @patch('sakia.core.Application')
+    @patch('sakia.core.Community')
+    @patch('time.time', Mock(return_value=50000))
+    def test_explore_full_from_extremity(self, app, community):
+        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
+        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
+        app.preferences = {'expert_mode': True}
+
+        explorer_graph = ExplorerGraph(app, community)
+
+        async def exec_test():
+            await explorer_graph._explore(self.idA, 5)
+            self.assertEqual(len(explorer_graph.nx_graph.nodes()), 5)
+            self.assertEqual(len(explorer_graph.nx_graph.edges()), 4)
+
+        self.lp.run_until_complete(exec_test())
+
     @patch('sakia.core.Application')
     @patch('sakia.core.Community')
     @patch('time.time', Mock(return_value=50000))
@@ -178,6 +195,10 @@ class TestExplorerGraph(unittest.TestCase, QuamashTest):
             self.assertTrue(task.cancelled())
             self.assertNotEqual(task, explorer_graph.exploration_task)
             task2 = explorer_graph.exploration_task
+            explorer_graph.start_exploration(self.idB, 2)
+            await asyncio.sleep(0)
+            self.assertTrue(task2.cancelled())
+            task3 = explorer_graph.exploration_task
             explorer_graph.stop_exploration()
             await asyncio.sleep(0)
             self.assertTrue(task2.cancelled())
diff --git a/src/sakia/tests/unit/core/graph/test_wot_graph.py b/src/sakia/tests/unit/core/graph/test_wot_graph.py
index bdfa851eb820050339acf252913e14b09276d24e..262f75a1c2fee5c82ca14107e478ad3a443765fb 100644
--- a/src/sakia/tests/unit/core/graph/test_wot_graph.py
+++ b/src/sakia/tests/unit/core/graph/test_wot_graph.py
@@ -5,7 +5,7 @@ from asynctest.mock import Mock, CoroutineMock, patch
 from PyQt5.QtCore import QLocale
 from sakia.tests import QuamashTest
 from sakia.core.graph import WoTGraph
-from sakia.core.graph.constants import ArcStatus, NodeStatus
+from sakia.core.graph.constants import EdgeStatus, NodeStatus
 
 
 class TestWotGraph(unittest.TestCase, QuamashTest):
diff --git a/src/sakia/tests/unit/core/test_account.py b/src/sakia/tests/unit/core/test_account.py
index 4648cd4666b69bb67a172d010e7709222e2e035c..8fae00475d12656daa12956d4d36599132c33816 100644
--- a/src/sakia/tests/unit/core/test_account.py
+++ b/src/sakia/tests/unit/core/test_account.py
@@ -1,12 +1,13 @@
+import sys
 import unittest
-from unittest.mock import patch, Mock
-from asynctest import CoroutineMock
+import asyncio
+import quamash
+import logging
 from PyQt5.QtCore import QLocale
 from sakia.core.registry.identities import IdentitiesRegistry
 from sakia.core import Account
 from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-from ucoinpy.api import bma
+
 
 class TestAccount(unittest.TestCase, QuamashTest):
     def setUp(self):
@@ -27,95 +28,3 @@ class TestAccount(unittest.TestCase, QuamashTest):
         self.assertEqual(account.pubkey, account_from_json.pubkey)
         self.assertEqual(len(account.communities), len(account_from_json.communities))
         self.assertEqual(len(account.wallets), len(account.wallets))
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    @patch('sakia.core.net.api.bma.access.BmaAccess')
-    def test_check_register_success(self, app, community, bma_access):
-        account = Account("test_salt", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                          "john", [], [], [], self.identities_registry)
-
-        def mock_simple_request(request, req_args, get_args={}):
-            if request is bma.wot.Lookup:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    return nice_blockchain.bma_lookup_john
-                elif req_args['search'] == "john":
-                    return nice_blockchain.bma_lookup_john
-            elif request is bma.wot.CertifiersOf:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    return nice_blockchain.bma_certifiers_of_john
-                elif req_args['search'] == "john":
-                    return nice_blockchain.bma_certifiers_of_john
-        community.bma_access = bma_access
-        bma_access.simple_request = CoroutineMock(side_effect=mock_simple_request)
-
-        async def exec_test():
-            result = await account.check_registered(community)
-            self.assertEqual(result, (True, "john",
-                                      "john"))
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    @patch('sakia.core.net.api.bma.access.BmaAccess')
-    def test_check_register_pubkey_failure(self, app, community, bma_access):
-        account = Account("test_salt", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                          "john", [], [], [], self.identities_registry)
-
-        def mock_simple_request(request, req_args, get_args={}):
-            if request is bma.wot.Lookup:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    raise ValueError('404 not found')
-                elif req_args['search'] == "john":
-                    result = nice_blockchain.bma_lookup_john
-                    result['pubkey'] = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-                    return result
-            elif request is bma.wot.CertifiersOf:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    raise ValueError('404 not found')
-                elif req_args['search'] == "john":
-                    result = nice_blockchain.bma_certifiers_of_john
-                    result['pubkey'] = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-                    return result
-        community.bma_access = bma_access
-        bma_access.simple_request = CoroutineMock(side_effect=mock_simple_request)
-
-        async def exec_test():
-            result = await account.check_registered(community)
-            self.assertEqual(result, (False, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                                      "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"))
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    @patch('sakia.core.net.api.bma.access.BmaAccess')
-    def test_check_register_uid_failure(self, app, community, bma_access):
-        account = Account("test_salt", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                          "john", [], [], [], self.identities_registry)
-
-        def mock_simple_request(request, req_args, get_args={}):
-            if request is bma.wot.Lookup:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    result = nice_blockchain.bma_lookup_john
-                    result['uid'] = "imnotjohn"
-                    return result
-                elif req_args['search'] == "john":
-                    raise ValueError('404 not found')
-            elif request is bma.wot.CertifiersOf:
-                if req_args['search'] == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ":
-                    result = nice_blockchain.bma_certifiers_of_john
-                    result['uid'] = "imnotjohn"
-                    return result
-                elif req_args['search'] == "john":
-                    raise ValueError('404 not found')
-        community.bma_access = bma_access
-        bma_access.simple_request = CoroutineMock(side_effect=mock_simple_request)
-
-        async def exec_test():
-            result = await account.check_registered(community)
-            self.assertEqual(result, (False, "john",
-                                      "imnotjohn"))
-
-        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/core/test_bma_access.py b/src/sakia/tests/unit/core/test_bma_access.py
index 6708d6937410b5c4b17add6a912321631e6f9890..293eb3ddf51a74dbc177c8b33a9825314c7e5380 100644
--- a/src/sakia/tests/unit/core/test_bma_access.py
+++ b/src/sakia/tests/unit/core/test_bma_access.py
@@ -44,8 +44,3 @@ class TestBmaAccess(unittest.TestCase, QuamashTest):
 
     def test_filter_nodes(self):
         pass#TODO
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/test_community.py b/src/sakia/tests/unit/core/test_community.py
index 8083564c98fcff928e4eeb27073bbe4116097b2b..acf5c176be84783ec2fabde14674f3cd9e50e9bd 100644
--- a/src/sakia/tests/unit/core/test_community.py
+++ b/src/sakia/tests/unit/core/test_community.py
@@ -25,9 +25,3 @@ class TestCommunity(unittest.TestCase, QuamashTest):
         community_from_json = Community.load(json_data)
         self.assertEqual(community.name, community_from_json.name)
         self.assertEqual(len(community.network._nodes), len(community_from_json.network._nodes))
-
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/test_identities.py b/src/sakia/tests/unit/core/test_identities.py
index e81dc9af2aae1996f832f7eca909d50432258ef8..f00d01847f6d5c886022d4e7dd5df5d985875d38 100644
--- a/src/sakia/tests/unit/core/test_identities.py
+++ b/src/sakia/tests/unit/core/test_identities.py
@@ -35,8 +35,3 @@ class TestIdentity(unittest.TestCase, QuamashTest):
                                                                    community)
         self.assertEqual(identity, identity_from_data)
 
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/test_identity.py b/src/sakia/tests/unit/core/test_identity.py
index ed8c95b83f006a84255f62456073c9b40083c21d..ec7044f771f6e5e28bfffb9bc5eb4c48a5960d2e 100644
--- a/src/sakia/tests/unit/core/test_identity.py
+++ b/src/sakia/tests/unit/core/test_identity.py
@@ -88,8 +88,3 @@ class TestIdentity(unittest.TestCase, QuamashTest):
 
         self.lp.run_until_complete(exec_test())
 
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/test_wallet.py b/src/sakia/tests/unit/core/test_wallet.py
index a3cdcdebaa54a171a0d859185aaedb5d3cb19c39..04253105d291e1a8ee4264cd646690ff2bc76be3 100644
--- a/src/sakia/tests/unit/core/test_wallet.py
+++ b/src/sakia/tests/unit/core/test_wallet.py
@@ -28,8 +28,3 @@ class TestWallet(unittest.TestCase, QuamashTest):
         self.assertEqual(wallet.pubkey, wallet_from_json.pubkey)
         self.assertEqual(wallet.name, wallet_from_json.name)
         self.assertEqual(wallet._identities_registry, wallet_from_json._identities_registry)
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py b/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py
index 8c106b46eebd4e4bc4fbbf53b67bcb4fa28cd2af..5ec06b989afe3b497e185407bbddb142738c306e 100644
--- a/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py
+++ b/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py
@@ -61,8 +61,3 @@ class TestTxHistory(unittest.TestCase, QuamashTest):
         dividends_value = sum([ud['amount'] for ud in self.wallet.dividends(self.community)])
         self.assertEqual(dividends_value, 15)
         mock.delete_mock()
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/unit/gui/views/__init__.py b/src/sakia/tests/unit/gui/views/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/sakia/tests/unit/gui/views/test_base_edge.py b/src/sakia/tests/unit/gui/views/test_base_edge.py
new file mode 100644
index 0000000000000000000000000000000000000000..f05353bd653e425dc05be4f841960b810d3e39f0
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_base_edge.py
@@ -0,0 +1,34 @@
+import unittest
+from PyQt5.QtCore import QLocale
+from sakia.tests import QuamashTest
+from sakia.gui.views.edges.base_edge import BaseEdge
+from sakia.core.graph.constants import EdgeStatus
+
+
+class TestBaseEdge(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_edge(self):
+        metadata = {
+            'status': EdgeStatus.STRONG
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            edge = BaseEdge("A", "B", metadata, nx_pos)
+            self.assertEqual(edge.source, "A")
+            self.assertEqual(edge.destination, "B")
+            self.assertEqual(edge.destination_point.x(), 10)
+            self.assertEqual(edge.destination_point.y(), 20)
+            self.assertEqual(edge.source_point.x(), 0)
+            self.assertEqual(edge.source_point.y(), 5)
+            self.assertEqual(edge.status, EdgeStatus.STRONG)
+
+        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_base_node.py b/src/sakia/tests/unit/gui/views/test_base_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..4162afe55fb9dcd61b44a60b675d70e154e84921
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_base_node.py
@@ -0,0 +1,37 @@
+import unittest
+from PyQt5.QtCore import QLocale
+from sakia.tests import QuamashTest
+from sakia.gui.views.nodes.base_node import BaseNode
+from sakia.core.graph.constants import NodeStatus
+
+
+class TestBaseNode(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_edge(self):
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "UserA",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = BaseNode(("A", metadata), nx_pos)
+            self.assertEqual(node.id, "A")
+            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
+            self.assertEqual(node.x(), 0)
+            self.assertEqual(node.y(), 5)
+            self.assertEqual(node.status_wallet, False)
+            self.assertEqual(node.status_member, True)
+            self.assertEqual(node.text, "UserA")
+            self.assertEqual(node.toolTip(), "TestTooltip")
+
+        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_edge.py b/src/sakia/tests/unit/gui/views/test_explorer_edge.py
new file mode 100644
index 0000000000000000000000000000000000000000..5732190cbca2a56b3f0e6cfa14cf4eb520f3ccdd
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_explorer_edge.py
@@ -0,0 +1,76 @@
+import unittest
+from unittest.mock import patch
+from PyQt5.QtCore import QLocale
+from sakia.tests import QuamashTest
+from sakia.gui.views.edges import ExplorerEdge
+from sakia.core.graph.constants import EdgeStatus
+
+
+class TestExplorerEdge(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_wot_edge(self):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 0)
+            self.assertEqual(edge.source, "A")
+            self.assertEqual(edge.destination, "B")
+            self.assertAlmostEqual(edge.destination_point.x(), 10.0, delta=5)
+            self.assertAlmostEqual(edge.destination_point.y(), 20.0, delta=5)
+            self.assertAlmostEqual(edge.source_point.x(), 10.0, delta=5)
+            self.assertAlmostEqual(edge.source_point.y(), 20.0, delta=5)
+            self.assertEqual(edge.status, EdgeStatus.STRONG)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter')
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_paint(self, painter, widget):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+
+        async def exec_test():
+            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 1)
+            edge.paint(painter, 0, widget)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter')
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_bounding_rect(self, painter, widget):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+
+        async def exec_test():
+            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 0)
+            bounding_rect = edge.boundingRect()
+            self.assertAlmostEqual(bounding_rect.x(), 7.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.y(), 17.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.width(), 6.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.height(), 6.0, delta=5)
+
+        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_node.py b/src/sakia/tests/unit/gui/views/test_explorer_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd54b19f534f54e2931d1f092d872f1d1f3bc993
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_explorer_node.py
@@ -0,0 +1,80 @@
+import unittest
+from unittest.mock import patch
+from PyQt5.QtCore import QLocale, QPointF
+from PyQt5.QtGui import QPainter
+from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget
+from sakia.tests import QuamashTest
+from sakia.gui.views.nodes import ExplorerNode
+from sakia.core.graph.constants import NodeStatus
+
+
+class TestExplorerNode(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_explorer_node(self):
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "UserA",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1)
+            self.assertEqual(node.id, "A")
+            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
+            self.assertEqual(node.x(), 0)
+            self.assertEqual(node.y(), 0)
+            self.assertEqual(node.status_wallet, False)
+            self.assertEqual(node.status_member, True)
+            self.assertEqual(node.text, "UserA")
+            self.assertEqual(node.toolTip(), "TestTooltip")
+
+        self.lp.run_until_complete(exec_test())
+
+    def test_paint(self):
+        painter = QPainter()
+        widget = QWidget()
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "UserA",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1)
+            node.paint(painter, QStyleOptionGraphicsItem(), widget)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter', spec=QPainter)
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_bounding_rect(self, painter, widget):
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "A",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1)
+            bounding_rect = node.boundingRect()
+            self.assertAlmostEqual(bounding_rect.x(), -0.5, delta=5)
+            self.assertAlmostEqual(bounding_rect.y(), -0.5, delta=5)
+            self.assertAlmostEqual(bounding_rect.width(), 19.59375, delta=5)
+            self.assertAlmostEqual(bounding_rect.height(), 37.0, delta=5)
+
+        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_scene.py b/src/sakia/tests/unit/gui/views/test_explorer_scene.py
new file mode 100644
index 0000000000000000000000000000000000000000..9585e42c4eca9f210996c4ceb6229402f51c3778
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_explorer_scene.py
@@ -0,0 +1,179 @@
+import unittest
+import networkx
+import math
+from unittest.mock import patch, Mock
+from sakia.tests import QuamashTest
+from sakia.gui.views.scenes import ExplorerScene
+from sakia.core.graph.constants import NodeStatus
+
+
+class TestExplorerScene(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        self.identities_uids = ['A', 'B', 'C', 'D', 'E']
+        self.identities_pubkeys = ['pbkA', 'pbkB', 'pbkC', 'pbkD', 'pbkE']
+        self.certifications = [('pbkA', 'pbkB'),
+                               ('pbkB', 'pbkC'),
+                               ('pbkD', 'pbkA'),
+                               ('pbkA', 'pbkE')]
+        # Graph :
+        #  A -> B -> C
+        #    <- D
+        #    -> E
+        self.identity_status = [NodeStatus.SELECTED, NodeStatus.NEUTRAL, NodeStatus.NEUTRAL,
+                                NodeStatus.OUT, NodeStatus.NEUTRAL]
+        self.test_graph = networkx.MultiDiGraph().to_undirected()
+        self.test_graph.add_nodes_from(self.identities_pubkeys)
+        self.test_graph.add_edges_from(self.certifications)
+        for index, node in enumerate(self.test_graph.nodes(data=True)):
+            node[1]['text'] = self.identities_uids[index]
+            node[1]['tooltip'] = self.identities_pubkeys[index]
+            node[1]['status'] = self.identity_status[index]
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_init_layout(self):
+        data_layout = ExplorerScene._init_layout(self.test_graph)
+        for pubkey in self.identities_pubkeys:
+            self.assertEqual(data_layout[pubkey]['theta'], None)
+            self.assertEqual(data_layout[pubkey]['scenter'], 25)
+            self.assertEqual(data_layout[pubkey]['nchild'], 0)
+            self.assertEqual(data_layout[pubkey]['sparent'], None)
+            self.assertEqual(data_layout[pubkey]['stsize'], 0.0)
+            self.assertEqual(data_layout[pubkey]['span'], 0.0)
+
+    def test_set_parent_nodes(self):
+        data_layout = ExplorerScene._init_layout(self.test_graph)
+        ExplorerScene._set_parent_nodes(self.test_graph, data_layout, 'pbkA')
+        self.assertEqual(data_layout['pbkA']['scenter'], 0)
+        self.assertEqual(data_layout['pbkB']['scenter'], 1)
+        self.assertEqual(data_layout['pbkC']['scenter'], 2)
+        self.assertEqual(data_layout['pbkD']['scenter'], 1)
+        self.assertEqual(data_layout['pbkE']['scenter'], 1)
+
+        self.assertEqual(data_layout['pbkA']['sparent'], None)
+        self.assertEqual(data_layout['pbkB']['sparent'], 'pbkA')
+        self.assertEqual(data_layout['pbkC']['sparent'], 'pbkB')
+        self.assertEqual(data_layout['pbkD']['sparent'], 'pbkA')
+        self.assertEqual(data_layout['pbkE']['scenter'], 1)
+
+        self.assertEqual(data_layout['pbkA']['nchild'], 3)
+        self.assertEqual(data_layout['pbkB']['nchild'], 1)
+        self.assertEqual(data_layout['pbkC']['nchild'], 0)
+        self.assertEqual(data_layout['pbkD']['nchild'], 0)
+        self.assertEqual(data_layout['pbkE']['nchild'], 0)
+
+    def test_set_subtree_size(self):
+        data_layout = ExplorerScene._init_layout(self.test_graph)
+
+        data_layout['pbkA']['sparent'] = None
+        data_layout['pbkB']['sparent'] = 'pbkA'
+        data_layout['pbkC']['sparent'] = 'pbkB'
+        data_layout['pbkD']['sparent'] = 'pbkA'
+        data_layout['pbkE']['sparent'] = 'pbkA'
+
+        data_layout['pbkA']['nchild'] = 2
+        data_layout['pbkB']['nchild'] = 1
+        data_layout['pbkC']['nchild'] = 0
+        data_layout['pbkD']['nchild'] = 0
+        data_layout['pbkE']['nchild'] = 0
+
+        ExplorerScene._set_subtree_size(self.test_graph, data_layout)
+        self.assertAlmostEqual(data_layout['pbkA']['stsize'], 3.0)
+        self.assertAlmostEqual(data_layout['pbkB']['stsize'], 1.0)
+        self.assertAlmostEqual(data_layout['pbkC']['stsize'], 1.0)
+        self.assertAlmostEqual(data_layout['pbkD']['stsize'], 1.0)
+        self.assertAlmostEqual(data_layout['pbkE']['stsize'], 1.0)
+
+    def test_set_subtree_span(self):
+        data_layout = ExplorerScene._init_layout(self.test_graph)
+
+        data_layout['pbkA']['sparent'] = None
+        data_layout['pbkB']['sparent'] = 'pbkA'
+        data_layout['pbkC']['sparent'] = 'pbkB'
+        data_layout['pbkD']['sparent'] = 'pbkA'
+        data_layout['pbkE']['sparent'] = 'pbkA'
+
+        data_layout['pbkA']['nchild'] = 2
+        data_layout['pbkB']['nchild'] = 1
+        data_layout['pbkC']['nchild'] = 0
+        data_layout['pbkD']['nchild'] = 0
+        data_layout['pbkE']['nchild'] = 0
+
+        data_layout['pbkA']['stsize'] = 3.0
+        data_layout['pbkB']['stsize'] = 1.0
+        data_layout['pbkC']['stsize'] = 1.0
+        data_layout['pbkD']['stsize'] = 1.0
+        data_layout['pbkE']['stsize'] = 1.0
+
+        data_layout['pbkA']['span'] = 2 * math.pi
+
+        ExplorerScene._set_subtree_spans(self.test_graph, data_layout, 'pbkA')
+        self.assertAlmostEqual(data_layout['pbkA']['span'], 2 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkB']['span'], 2 / 3 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkC']['span'], 2 / 3 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkD']['span'], 2 / 3 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkE']['span'], 2 / 3 * math.pi)
+
+    @patch('networkx.MultiDiGraph')
+    def test_set_subtree_position(self, mock_graph):
+        # We mock the edges generator to ensure the order in which they appear
+        mock_graph.edges = Mock(return_value=self.certifications)
+        data_layout = {}
+
+        for pubkey in self.identities_pubkeys:
+            data_layout[pubkey] = {
+                'theta': None
+            }
+
+        data_layout['pbkA']['sparent'] = None
+        data_layout['pbkB']['sparent'] = 'pbkA'
+        data_layout['pbkC']['sparent'] = 'pbkB'
+        data_layout['pbkD']['sparent'] = 'pbkA'
+        data_layout['pbkE']['sparent'] = 'pbkA'
+
+        data_layout['pbkA']['nchild'] = 2
+        data_layout['pbkB']['nchild'] = 1
+        data_layout['pbkC']['nchild'] = 0
+        data_layout['pbkD']['nchild'] = 0
+        data_layout['pbkE']['nchild'] = 0
+
+        data_layout['pbkA']['span'] = 2 * math.pi
+        data_layout['pbkB']['span'] = 2 / 3 * math.pi
+        data_layout['pbkC']['span'] = 2 / 3 * math.pi
+        data_layout['pbkD']['span'] = 2 / 3 * math.pi
+        data_layout['pbkE']['span'] = 2 / 3 * math.pi
+
+        data_layout['pbkA']['theta'] = 0.0
+        ExplorerScene._set_positions(mock_graph, data_layout, 'pbkA')
+        self.assertAlmostEqual(data_layout['pbkA']['theta'], 0.0)
+        self.assertAlmostEqual(data_layout['pbkB']['theta'], 1 / 3 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkC']['theta'], 1 / 3 * math.pi)
+        self.assertAlmostEqual(data_layout['pbkD']['theta'], math.pi)
+        self.assertAlmostEqual(data_layout['pbkE']['theta'], 5 / 3 * math.pi)
+
+    @patch('networkx.MultiDiGraph')
+    @patch('networkx.MultiGraph')
+    @patch('networkx.shortest_path_length', return_value={'pbkA': 0, 'pbkB': 1, 'pbkC': 2, 'pbkD': 1, 'pbkE': 1})
+    def test_twopi_layout(self, mock_graph, mock_undirected, mock_paths):
+        # We mock the edges generator to ensure the order in which they appear
+        mock_graph.edges = Mock(return_value=self.certifications)
+        mock_graph.nodes = Mock(return_value=self.identities_pubkeys)
+        mock_graph.to_undirected = Mock(return_value=mock_undirected)
+        mock_undirected.nodes = Mock(return_value=self.identities_pubkeys)
+        mock_undirected.edges = Mock(return_value=self.certifications)
+
+        pos = ExplorerScene.twopi_layout(mock_graph, 'pbkA')
+
+        self.assertAlmostEqual(pos['pbkA'][0], 1 * math.cos(0.0) * 100)
+        self.assertAlmostEqual(pos['pbkB'][0], 2 * math.cos(1 / 3 * math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkC'][0], 3 * math.cos(1 / 3 * math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkD'][0], 2 * math.cos(math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkE'][0], 2 * math.cos(5 / 3 * math.pi) * 100)
+
+        self.assertAlmostEqual(pos['pbkA'][1], 1 * math.sin(0.0) * 100)
+        self.assertAlmostEqual(pos['pbkB'][1], 2 * math.sin(1 / 3 * math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkC'][1], 3 * math.sin(1 / 3 * math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkD'][1], 2 * math.sin(math.pi) * 100)
+        self.assertAlmostEqual(pos['pbkE'][1], 2 * math.sin(5 / 3 * math.pi) * 100)
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_wot_edge.py b/src/sakia/tests/unit/gui/views/test_wot_edge.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c0632b97e15f9e5eace680549297129cec79ba9
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_wot_edge.py
@@ -0,0 +1,76 @@
+import unittest
+from unittest.mock import patch
+from PyQt5.QtCore import QLocale
+from sakia.tests import QuamashTest
+from sakia.gui.views.edges import WotEdge
+from sakia.core.graph.constants import EdgeStatus
+
+
+class TestWotEdge(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_wot_edge(self):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            edge = WotEdge("A", "B", metadata, nx_pos)
+            self.assertEqual(edge.source, "A")
+            self.assertEqual(edge.destination, "B")
+            self.assertEqual(edge.destination_point.x(), 10)
+            self.assertEqual(edge.destination_point.y(), 20)
+            self.assertEqual(edge.source_point.x(), 0)
+            self.assertEqual(edge.source_point.y(), 5)
+            self.assertEqual(edge.status, EdgeStatus.STRONG)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter')
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_paint(self, painter, widget):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+
+        async def exec_test():
+            edge = WotEdge("A", "B", metadata, nx_pos)
+            edge.paint(painter, 0, widget)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter')
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_bounding_rect(self, painter, widget):
+        metadata = {
+            'status': EdgeStatus.STRONG,
+            'confirmation_text': "0/6"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+
+        async def exec_test():
+            edge = WotEdge("A", "B", metadata, nx_pos)
+            bounding_rect = edge.boundingRect()
+            self.assertAlmostEqual(bounding_rect.x(), -3.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.y(), 2.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.width(), 16.0, delta=5)
+            self.assertAlmostEqual(bounding_rect.height(), 21.0, delta=5)
+
+        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_wot_node.py b/src/sakia/tests/unit/gui/views/test_wot_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..a92ba2daa72a6309c08b885178f3a16274b989db
--- /dev/null
+++ b/src/sakia/tests/unit/gui/views/test_wot_node.py
@@ -0,0 +1,80 @@
+import unittest
+from unittest.mock import patch
+from PyQt5.QtCore import QLocale
+from PyQt5.QtGui import QPainter
+from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget
+from sakia.tests import QuamashTest
+from sakia.gui.views.nodes import WotNode
+from sakia.core.graph.constants import NodeStatus
+
+
+class TestWotNode(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    def test_create_wot_node(self):
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "UserA",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = WotNode(("A", metadata), nx_pos)
+            self.assertEqual(node.id, "A")
+            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
+            self.assertEqual(node.x(), 0)
+            self.assertEqual(node.y(), 5)
+            self.assertEqual(node.status_wallet, False)
+            self.assertEqual(node.status_member, True)
+            self.assertEqual(node.text, "UserA")
+            self.assertEqual(node.toolTip(), "TestTooltip")
+
+        self.lp.run_until_complete(exec_test())
+
+    def test_paint(self):
+        painter = QPainter()
+        widget = QWidget()
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "UserA",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = WotNode(("A", metadata), nx_pos)
+            node.paint(painter, QStyleOptionGraphicsItem(), widget)
+
+        self.lp.run_until_complete(exec_test())
+
+    @patch('PyQt5.QtGui.QPainter', spec=QPainter)
+    @patch('PyQt5.QtWidgets.QWidget')
+    def test_bounding_rect(self, painter, widget):
+        metadata = {
+            'status': NodeStatus.NEUTRAL,
+            'text': "A",
+            'tooltip': "TestTooltip"
+        }
+        nx_pos = {
+            "A": (0, 5),
+            "B": (10, 20)
+        }
+        async def exec_test():
+            node = WotNode(("A", metadata), nx_pos)
+            bounding_rect = node.boundingRect()
+            self.assertAlmostEqual(bounding_rect.x(), -0.5, delta=1)
+            self.assertAlmostEqual(bounding_rect.y(), -0.5, delta=1)
+            self.assertAlmostEqual(bounding_rect.width(), 19.59375, delta=5)
+            self.assertAlmostEqual(bounding_rect.height(), 37.0, delta=5)
+
+        self.lp.run_until_complete(exec_test())