Wiki Discussion: Generating Cytoscape.js graphs with SWI-Prolog

This is some prototype code along the way for the proof of concept. It still requires a few manual steps but since prototype code is useful for converting into tutorials because it works and doesn’t drop all of the moving parts in the final version it is posted here.

Click triangle to expand

Use: Create module.jorunal - Collect a set of facts that can make a graph (nodes and edges) and save to a file using library(persistency).
File: persist module facts.pl

:- module(persist_module_facts,[
        init/0,
        list_predicates_import_from/1,
        list_predicate_import_from/1,
        list_imports/1
    ]).

% -----------------------------------------------------------------------------

:- use_module(library(persistency)).

:- persistent
    predicates_import_from(import_module:atom,export_module:atom),
    predicate_import_from(import_module:atom,export_module:atom,predicate_indicator:compound),
    imports(import_module:atom,export_module:atom).

:- initialization(main).

main :-
    db_attach('module.journal', []),
    init.

add_predicates_import_from(Import_module,Export_module) :-
    (
        predicates_import_from(Import_module,Export_module), !
    ;
        assert_predicates_import_from(Import_module,Export_module)
    ).

add_predicate_import_from(Import_module,Export_module,Name/Arity) :-
    (
        predicate_import_from(Import_module,Export_module,Name/Arity), !
    ;
        assert_predicate_import_from(Import_module,Export_module,Name/Arity)
    ).

add_imports(Import_module,Export_module) :-
    (
        imports(Import_module,Export_module), !
    ;
        assert_imports(Import_module,Export_module)
    ).

init_imported_from :-
    forall(
            predicate_property(Import_module:Predicate, imported_from(Export_module)),
            (
                add_predicates_import_from(Import_module,Export_module),
                functor(Predicate,Name,Arity),
                add_predicate_import_from(Import_module,Export_module,Name/Arity)
            )
        ).

init_import_module :-
    forall(
            (
                current_module(Import_module),
                import_module(Import_module,Export_module)
            ),
            add_imports(Import_module,Export_module)
        ).

init :-
    init_imported_from,
    init_import_module.

list_predicates_import_from(predicates_import_from(Import_module,Export_module)) :-
    predicates_import_from(Import_module,Export_module).

list_predicate_import_from(predicate_import_from(Import_module,Export_module,Predicate)) :-
    predicate_import_from(Import_module,Export_module,Predicate).

list_imports(imports(Import_module,Export_module)) :-
    imports(Import_module,Export_module).

Example run:

?- working_directory(_,'C:/Users/Groot/Documents').
true.

?- ['persist module facts'].
true.

?- halt.

halt. is needed to write the facts out to the file module.jorunal

Use: Create elements.js - Convert the facts from the persistent file and format them for use with Cytoscape.js. Cytoscape.js can read the elements (nodes and edges) as JSON and by packing the JSON into a JavaScript variable the code to use it can hopefully become boilerplate code.
File: Cytoscape.js elements - DCG generator.pl

:- module('Cytoscape.js elements - DCG generator',[
      predicates_import_from/2,
      predicate_import_from/3,
      imports/2,
      write_elements_var/1
   ]).

% -----------------------------------------------------------------------------

:- use_module(library(persistency)).
:- use_module(library(fileutils)).

:- set_prolog_flag(double_quotes, codes).

:- persistent
    predicates_import_from(import_module:atom,export_module:atom),
    predicate_import_from(import_module:atom,export_module:atom,predicate_indicator:compound),
    imports(import_module:atom,export_module:atom).

:- initialization(db_attach('C:/Users/Groot/Documents', [])).

write_elements_var(Filepath) :-
   nodes(Nodes),
   edges(Edges),
   cytoscape_js_elements(Nodes,Edges,Elements_var),
   with_output_to_file(Filepath,write(Elements_var)).

nodes(Nodes) :-
   setof(node(A),B^predicates_import_from(A,B),From),
   setof(node(B),A^predicates_import_from(A,B),To),
   ord_union(From,To,Nodes), !.

edges(Edges) :-
   setof(edge(A,B),predicates_import_from(A,B),Edges).

cytoscape_js_elements(Nodes,Edges,Elements_var) :-
   phrase(elements_var(Nodes,Edges),Codes,[]),
   string_codes(Elements_var,Codes).

% The JSON is passed as the JavaScript variable Elements.
elements_var(Nodes,Edges) -->
   "// Elements is JSON\n// See: https://js.cytoscape.org/#notation/elements-json\nvar Elements = ",
   elements(Nodes,Edges),
   ";".

% This starts the JSON part.
elements(Nodes,Edges) -->
   "{\n",
   "\tnodes: [\n",
   nodes_first(Nodes),
   "\n\t]",
   ",\n",
   "\tedges: [\n",
   edges_first(Edges),
   "\n\t]\n",
   "}".

nodes_first([H|T]) -->
   node(H),
   nodes_rest(T).

nodes_rest([H|T]) -->
   ",\n",
   node(H),
   nodes_rest(T).
nodes_rest([]) --> [].

node(node(Id)) -->
   { string_codes(Id,Id_codes) },
   "\t\t{ data: { id: \"",
   Id_codes,
   "\" } }".

edges_first([H|T]) -->
   edge(H),
   edges_rest(T).

edges_rest([H|T]) -->
   ",\n",
   edge(H),
   edges_rest(T).
edges_rest([]) --> [].

edge(edge(From,To)) -->
   {
      string_codes(From,From_codes),
      string_codes(To,To_codes)
   },
   "\t\t{ data: { source: \"",
   From_codes,
   "\", target: \"",
   To_codes,
   "\" } }".

Example run:

?- working_directory(_,'C:/Users/Groot/Documents').
true.

?- ['Cytoscape.js elements - DCG generator'].
true.

?- write_elements_var('elements.js').
true.

Cytoscape.js is JavaScript run in HTML.

File: index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Cytoscape.js - Basic example for Prolog facts. Uses layout extension <span style="font-weight:bold">cola</span></title>
        <link rel="stylesheet" href="style.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.15.2/cytoscape.min.js" integrity="sha512-PqivlaNWoXvHYlvku80fbWO/yBiRmGhISj5uVdAodyqsGprDcVZy6aecJDVNE0fIIE/YeiOzp5yJTR5ZjFlR4Q==" crossorigin="anonymous"></script>
        <script src="https://unpkg.com/webcola@3.3.8/WebCola/cola.min.js"></script>
        <script src="https://unpkg.com/cytoscape-cola@2.2.3/cytoscape-cola.js"></script>
        <script src="script.js"></script>
        <script src='elements.js'></script>
        <script src='style.js'></script>
        <script src='layout.js'></script>
        </head>
    <body>
        <div id="cy"></div>
    </body>
</html>

File: style.css

#cy {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0px;
    left: 0px;
}

File: script.js

document.addEventListener("DOMContentLoaded", function () {
    var cy = cytoscape(
        {
            container: document.getElementById('cy'),
            elements: Elements,
            style: Style,
            layout: Layout
        }
    );
});

File: style.js

// Style is JSON
// Think CSS converted to JSON
var Style = [
    {
        "selector": "node",
        "style": {
            "label": "data(id)"
        }
    },
    {
        "selector": "edge",
        "style": {
            'curve-style': 'haystack',
        }
    }
];

File: layout.js

// Layout is JSON
var Layout = {
    name: 'cola'
}

To see the graph just open index.html with an Internet browser.
In Windows 10 I just double click on index.html within File Explorer.

I know it is not pretty but it was not meant to be pretty at this point, it is a proof of concept.

1 Like