Skip to content
This repository has been archived by the owner on Jan 20, 2023. It is now read-only.

marklogic-community/grove-vue-visjs-graph

Repository files navigation

MarkLogic Grove Vue Visjs Graph

This library provides Vue components providing an interactive graph visualization of nodes and edges, as well an interactive timeline visualization of (grouped) items. It is a wrapper for the vanilla JS ml-visjs-graph library, which itself is based on the VisJS Network library and the VisJS Timeline library.

The library is part of the MarkLogic Grove project, but could work in any Vue application.

QuickStart

First, add the grove-vue-visjs-graph dependency via npm. (In a Grove Project, you will want to do this inside the ui directory.)

npm install --save @marklogic-community/grove-vue-visjs-graph

For now you also need to install ml-visjs-graph directly:

npm install --save ml-visjs-graph

The latter depends on the vis package, which will get installed as dependency.

Then, in your Vue application, import VisjsGraph and/or VisjsTimeline as well as necessary styling:

// Either add this to ui/src/main.js to add it globally:
import { VisjsGraph, VisjsTimeline } from 'grove-vue-visjs-graph';
import 'vis/dist/vis.css';
import 'ml-visjs-graph/less/ml-visjs-graph.js.less';
Vue.component(VisjsGraph.name, VisjsGraph);
Vue.component(VisjsTimeline.name, VisjsTimeline);

// Or do this in a Vue page/component to add it there only:
import { VisjsGraph, VisjsTimeline } from 'grove-vue-visjs-graph';
import 'vis/dist/vis.css';
import 'ml-visjs-graph/less/ml-visjs-graph.js.less';

export default {
  ...,
  components: {
    ...,
    VisjsGraph,
    VisjsTimeline
  },
  ...
};

After that you can start using the directly, which could look for example like this:

Graph:

            <visjs-graph :nodes="nodes" :edges="edges" :options="graphOptions" layout="standard" :events="graphEvents"></visjs-graph>

Timeline:

            <visjs-timeline :items="items" :groups="groups" :options="timelineOptions" :events="timelineEvents"></visjs-timeline>

Recommended setup for grove-vue-template for VisjsGraph and VisjsTimeline

It is easiest to import the component globally via main.js as described in previous section. Then add this to the template of the DetailPage (ui/src/views/DetailPage.vue) within the existing b-tabs, at last position:

          <b-tab title="Graph”>
            <visjs-graph v-if="tabIndex === 3" :nodes="nodes" :edges="edges" :options="graphOptions" layout="standard" :events="graphEvents"></visjs-graph>
          </b-tab>

Note: same might apply to Timeline, haven't tested yet.

Note that tabIndex === 3 to enforce that the graph is only painted when the tab is open. The graph won't show properly if painted while hidden, as it relies on panel sizes and such.

Graph:

At minimum, you need to initialize nodes, edges, graphOptions, and graphEvents. Though, it can be convenient to use nodesCache, and edgesCache, and use so-called computed nodes and edges. The cache object can be used to quickly check if a node or edge exists, and you can fetch its details very quickly from it. It would look like this:

  data() {
    return {
      ...,
      nodesCache: {},
      edgesCache: {},
      graphOptions: {},
      graphEvents: {}
    };
  },
  computed: {
    ...,
    nodes() {
      return Object.values(this.nodesCache);
    },
    edges() {
      return Object.values(this.edgesCache);
    }
  }

Timeline:

At minimum, you need to initialize items, groups, timelineOptions, and timelineEvents. Though, it can be convenient to use itemsCache, and groupsCache, and use so-called computed items and groups. The cache object can be used to quickly check if an item or a group exists, and you can fetch its details very quickly from it. It would look like this:

  data() {
    return {
      ...,
      itemsCache: {},
      groupsCache: {},
      timelineOptions: {},
      timelineEvents: {}
    };
  },
  computed: {
    ...,
    items() {
      return Object.values(this.itemsCache);
    },
    groups() {
      return Object.values(this.groupsCache);
    }
  }

All component properties are automatically monitored for changes via the Observer pattern. Computed properties are automatically recalculated when a depending property gets changed, but really only when it changes!

Adding interaction with MarkLogic for VisjsGraph

The component includes a library that can make appropriate MarkLogic calls to /v1/graphs/sparql. Eventually, we would like to replace this library with a default Grove middle-tier endpoint. For now, you'll need to enable the legacy proxy (the whitelistProxyRoute from the legacy-routes package) in Grove. You can find the config in middle-tier/routes/index.js:

const enableLegacyProxy = true;

In that same file, you configure that legacy proxy. Add or uncomment at least the following:

        {
          endpoint: '/graphs/sparql',
          methods: ['post'], // no need for 'get'
          authed: true
        }

And make sure commas between other endpoint rules are applied correctly.

When you have the middle-tier running in development mode (with a straight npm start), it will restart automatically. If not, restart it manually to enable the change.

Next, you can start using it in the DetailPage. Start with importing the GraphApi, near the top of the script tag inside ui/src/views/DetailPage.vue:

import GraphApi from 'grove-vue-visjs-graph/src/api/GraphApi.js';

Next, you add a method to update the nodes and edges, just for convenience:

  methods: {
    ...,
    updateGraph(response) {
      const self = this;
      self.nodesCache = response.nodes;
      self.edgesCache = response.edges;
    }
  }

And then you use it to first initialize the graph by feeding it the result of GraphApi.expand:

  created() {
    const self = this;
    this.$store
      .dispatch('crud/' + self.type + '/view', {
        id: self.id,
        view: 'metadata'
      })
      .then(function(response) {
        if (!response.isError) {
          var metadata = JSON.parse(response.response);

          ...

          GraphApi.expand([metadata.uri]).then(self.updateGraph);
        }
      });
  },

Next, do the same in graphEvents to intercept double-click:

      graphEvents: {
        doubleClick(params) {
          if (params.nodes[0]) {
            GraphApi.expand(params.nodes, self.nodesCache, self.edgesCache).then(self.updateGraph);
          }
        }
      }

Note that nodesCache and edgesCache are passed in here, which is needed to prevent edges from being added multiple times, and getting miscounted.

VisJS provides many event hooks for you to add behavior or draw on the canvas, doubleClick is just one of them. See the VisJS documentation for other events. click and onContext are useful ones as well, and you can inspect the event argument to look for modifier keys.

Styling your VisjsGraph

To give some pointers about polishing how the graph looks, and feels: you can do a lot of tweaking with graphOptions. Most interesting is setting a different default, and leveraging groups. The GraphApi by default looks for rdf:type links, and uses the object iri as group name for the nodes. Something like the following gives you nice fancy fontawesome icons when running this against the sample-data that comes with the grove ui templates:

      graphOptions: {
        nodes: {
          shape: 'dot'
        },
        groups: {
          'http://xmlns.com/foaf/0.1/Person': {
            shape: 'icon',
            icon: {
              face: 'FontAwesome',
              code: '\uf007', // fa-user icon
              color: 'green'
            }
          }
        }
      },

For more detail on available options, see: http://visjs.org/docs/network/#options

Directive options

Out of the box, grove-vue-visjs-graph does not add any defaults on top of visjs, but ml-visjs-graph does. It tries to bundle some experience with bigger graphs, and large updates. You can override however, and you can also influence how the graph is displayed initially. ml-visjs-graph provides a ui toolbar that allows enabling/disabling physics, and picking a different layout for instance, but you can also influence them with directive options. One for layout is given above. A full list of available layouts include:

  • "standard" (force-directed)
  • "hierarchyTop"
  • "hierarchyBottom"
  • "hierarchyLeft"
  • "hierarchyRight"

Next to that you can specify physics, or just turn it off by passing in physics="false".

Further Reading

It is recommended to becoming familiar with the documentation for a VisJS network and VisJS timeline to take full benefit of these components.