By year

  1. 2018 (2)
  2. 2017 (4)
  3. 2015 (9)

Cytoscape.js powered Interactive Graph Visualization of WordNet Synsets

JavaScript graph visualization NLP WordNet

Posted on May 6

Cytoscape.js is a JavaScript library for graph visualization that enables rendering network structures on a web browser. The official site has some demonstrations that show its capabilities. I was fascinated and started playing with its easy-to-use API.

Also, I was interested in WordNet, which has nothing to do with Cytoscape.js, and wanted a tool for it. WordNet is a large lexical database that groups synonymous words into an unordered set called "synset" and contains relations among synsets. Out of curiosity, I wrote some queries to examine what is inside the Japanese WordNet database. Then, I tried visualizing the results with Cytoscape.js. Finally, I ended up building the following simple web app.

The Web App

WordNet Viewer (Heads up! This site is a little energy consuming. Do not visit when the battery is running low:)

WordNet Viewer

Graph Rendering

A graph is displayed at the center of the page.

Each vertex of the graph represents a synset and each edge represents relationship between two synsets. The node at the tip of an arrow is a synset of the hypernym (in most cases) of the synset at the origin. (Some arrows represent other relationships such as membership relation but the app does not make clear distinction among them.)

Nodes and edges can be moved around by dragging. And the viewport can be panned and zoomed by dragging the background and using mouse wheel.

Entering a Word

Typing a word in the text box at the topmost navbar and hitting Go button causes the app to look up the synset containing the word entered. If a synset is found, the app visualizes the network involves it.

The following example is a graph rendered for a word "programmer:"


Details of a synset

Clicking a node causes the app to change the layout of the graph to show all the words in the synset of the clicked node.


Search Buttons

Each word in a synset can be clicked to show a tooltip carrying some buttons. One of them navigates you to Google for instance.


The cytoscape-qtip plugin is used to enable this.

Implementation Details

The web app is made up of the tree parts below:

  • Synset look up in the WordNet SQLite database using sequel Ruby Gem.
  • Sinatra routes to invoke SQL queries.
  • Frontend script using Cytoscape.js to visualize the graph returned from Sinatra server.


The advantage of using Cytoscape.js is not only rendering of graphs but also builtin support for standard gestures to pan and zoom the viewport and many many others. Once we have graph data in a format taylored to Cytoscape.js, we are ready to enjoy the features provided by the library.

Below are very short excerpts (modified and simplified) from the code in the app.


A sample code snippet for initializing visualization of a given graph:

var cy = cytoscape({ // Initialization of Cytoscape core object.
  container: document.getElementById('cy'), // A DOM element to contain a graph rendering.
  elements: graph, // A graph data in JSON.
  style: [
      selector: 'node',
      style: {
        label: 'data(label)'

A CSS-like selector for selector specifies elements in a given graph. Some examples of selector:

selector objects selected
node All nodes.
edge All edges.
#foo A graph element ele where ele.data.id is "foo".
.foo A graph element classed foo.
[some_parameter = "some value"] Graph elements ele where ele.data.some_parameter is "some value".
[?some_parameter] Graph elements ele where ele.data.some_parameter is a truthy value.
:visible Visible graph elements.

In the above example, data(label) is specified to show ele.data.label as a label for an element ele selected by the selector.

Graph Data

A sample graph data consisting of two vertices and an edge connecting them (from source to target):

  elements: [
    // Two vertices
      group: "nodes",
      data: {id: "node1", label: "label for node1"}
      group: "nodes",
      data: {id: "node2", label: "label for node2"}
    // An edge from "node1" to "node2"
      group: "edges",
      data: {id: "node1-to-node2", source: "node1", target: "node2"}


An example use of dijkstra where the distance from the node with truthy data.start to the given node n is calculated:

var dijkstra = cy.elements().dijkstra('[?start]', function() {return 1});
var dist = dijkstra.distanceTo(n);

Style and Layout

A style sample where a mapper mapData is used to linearly change the width of a node according to its data.weight value:

        selector: 'node',
        style: {
          'shape': 'circle',
          'width': 'mapData(weight, 0, 10, 20, 60)',
          'label': 'data(label)',
          /* ... */

An example use of the concentric layout to position nodes so that they are centered around some node:

    name: 'concentric',
    concentric: function(n) {
      var d = dijkstra.distanceTo(n);
      return cy.nodes().length - d;
    levelWidth: function(nodes) {return 1;}

The positions of nodes are determined by the concentric function. The higher the return value of the function applied to a node, the nearer the center it is placed.

Querying WordNet Database with Ruby

The graph data is constructed by firstly querying SQLite 3 database of Japanese WordNet and secondly formatting the retrieved results for Cytoscape.js.


There is a Ruby Gem wordnet but we cannot use it for Japanese WordNet because its schema seems to be different from English WordNet.

Anyway, we can query the database directly. The sequel gem is quite handy for such tasks because we can construct SQL queries by chaining methods as is often done in Ruby.

A sample code for assigning a new Sequel instance to an instance variable @db of some (arbitrary) class based on the SQLite 3 database file ./wnjpn.sqlite3:

require 'sequel'
# ...
  @db = Sequel.sqlite('./wnjpn.sqlite3')

Synset Look Up from Word

To look up synsets from a given word lemma lemma, we specify the word table (@db[:word]), join the sense table, and select columns of interest.

  def get_word_synsets(lemma)
    words = @db[:word].where(:lemma => lemma)
    senses = words.join(:sense, :wordid => :wordid)
    senses.select(:synset, Sequel.qualify(:word, :lang), Sequel.qualify(:word, :lemma))

Sequel.qualify(:word, :lemma) can be written also as Sequel[:word][:lemma].

The corresponding SQL query is as follows:

SELECT "synset", "word"."lang", "word"."lemma" FROM "word"
  INNER JOIN "sense" ON ("sense"."wordid" = "word"."wordid")
  WHERE ("lemma" = lemma)

Finding Hypernyms

The synlink table (@db[:synlink]) has pairs of (synset1, synset2) each of which has a type link. We want pairs of link type hype. Straightforward in Sequel:

  def synset_to_hypernyms(synset)
    @db[:synlink].where(:synset1 => synset).where(link: 'hype')
      .select(:synset1, :synset2)

The corresponding SQL query is as follows:

SELECT "synset1", "synset2" FROM "synlink" WHERE (("synset1" = synset) AND ("link" = "hype"))


2015 My gh-pages