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

This is a topic to discuss the Wiki

Generating Cytoscape.js graphs with SWI-Prolog

1 Like

For those wanting to get a taste of creating Cytoscape.js graphs using SWI-Prolog read Convert facts to GraphViz dot file using DCGs then find and work the tutorials in the Cytoscape.js blog.

The tutorials seem to be dated and gloss over some needed points so they are not a straight forward following the tutorial, you will have to draw upon your knowledge of HTML, JavaScript, CSS, JSON and SVG to get them to work.

I have completed the first tutorial: Getting started with Cytoscape.js, and currently working through Visualizing Glycolysis with Cytoscape.js.


In searching for a way to use graphs more interactively ran across animations with D3. D3 has animations, and it appears Cytoscape.js might have something similar, but have not found a similar working example just yet.

Check out this D3 animation and then think about teaching Prolog to new users needing a visual animated example as an answer and you start to see how much these become useful.

Here is an example of a Cytoscape.js animation: Animated BFS


Related:

swish_cytoscape_js
https://swish.swi-prolog.org/example/render_c3.swinb


Personal notes

The Cytoscape.js tutorial demo gives a very simple demonstration of what can be done with Cytoscape.js. The code in in a GitHub repository that is designed for adding custom elements systems and Stylesheets and/or layouts.


Note: Using background-image one has to be sensitive to: CORS header ‘Access-Control-Allow-Origin’ missing.
Cross-Origin Resource Sharing (CORS)


node.js - Useful for running JavaScript as a server.

node.js http-server is a simple, zero-configuration command-line http server. It is powerful enough for production usage, but it’s simple and hackable enough to be used for testing, local development, and learning.

Steps to test Cytoscape.js tutorials on a local machine. Think workaround to avoid CORS errors.

  1. Download and install node.js
    Download | Node.js
  2. Install http-server package
    npm install --global http-server
  3. Change to directory that is the root of the files for the web site
  4. Create batch file to set Windows Path and start http-server
SET PATH="C:\Program Files (x86)\nodejs";"C:\Users\<user>\AppData\Roaming\npm";"C:\Users\<user>\AppData\Roaming\npm\node_modules\http-server"
http-server --cors

Start http-server by running batch file
http_server.bat
Using Internet browser access web pages
http://127.0.0.1:8080

This is also useful for access individual image files to make sure they load and display, e.g.

http://127.0.0.1:8080/glucose.svg

[Mon Aug 24 2020 15:03:50 GMT-0400 (Eastern Daylight Time)]  "GET /glucose.svg" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"

When running the Node.js http-server it will list actions and errors in the Windows console.

[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/13bpg.svg" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/glucose.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/g6p.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/f6p.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/f16bp.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/2pg.svg" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/pep.svg" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/gadp.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/13bpg.svg" Error (404): "Not found"
[Tue Aug 25 2020 07:46:34 GMT-0400 (Eastern Daylight Time)]  "GET /assets/pyruvate.svg" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041"

Remember to press F12 key when running the Internet browser to see any errors. This is where the CORS errors are reported.


Chrome Internet browser

Chrome Dev Tools

How to Completely Disable Cache in Google Chrome

Google Chrome - Clear cache & cookies

Open Chrome debugger in separate window (ref)

View Page Resources With Chrome DevTools
such as HTML, CSS, JavaScript, JSON. (ref)


SVG node background tutorial
http://embed.plnkr.co/wLuoon/

To see image height and width of an image in Chrome: Image Size Info - Browser Extension

JavaScript Errors - Throw and Try to Catch

Create SVG file by drawing.
Online tool: Method Draw
Application: Inkscape

Scalable Vector Graphics (SVG) 1.1 (Second Edition)

Should you use or learn jQuery in 2020?

Alignment constraints with guidelines

cytoscape - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers

Web Open Font Format

JSONLint - The JSON Validator

Why are there warnings about DevTools failed to load source map?
See: Use a source map

Cytoscape.js can import/export the elements as JSON (cy.json()) and supports two different formats (ref)

  1. array of collections
  2. array of elements

NB The nodes for a graph are AFAIK not accessible from from an Internet Browser console. They are obviously accessible in from JavaScript of the source document.

To export the cytoscape graph as JSON

  1. Create a button in the HTML body tag
<input type='button' id='advance' value='Export'>
  1. Add a listener event for the button. This goes within document.addEventListener("DOMContentLoaded", function ()
document.addEventListener("DOMContentLoaded", function () {
var toJSONButton = document.getElementById('advance');
    toJSONButton.addEventListener('click', function () {
        var result = toJsonButton();
    });
}
  1. Add handler for listener. This goes within document.addEventListener("DOMContentLoaded", function ()
document.addEventListener("DOMContentLoaded", function () {
    function toJsonButton() {
        var cyFlatJson = cy.json(true);
        var cyGroupJson = cy.json(false);
        console.log(JSON.stringify(cyFlatJson));
        console.log(JSON.stringify(cyGroupJson));
    }
});

Clicking on the Export button will write two strings to the browser console. The browser console is accessible by clicking F12 in an open Internet browser.


GitHub repositories
Topics: Cytoscape.js

cytoscape/cytoscape.js
plotly/dash-cytoscape - Has some interesting ideas and examples. Uses Python with Cytoscape.js

i-Vis at Bilkent - i-Vis Research Lab at Bilkent University
Has many repositories related to Cytoscape.js - The code is worth studying for examples.

Newt Pathway Viewer & Editor - Newt is a free, web based, open source viewer and editor for pathways in Systems Biological Graphical Notation (SBGN) and Simple Interaction Format (SIF). It was written with a series of libraries and extensions based on Cytoscape.js with utmost customization in mind. (Think Visio).
http://web.newteditor.org/ - Press F12 to see code.

demonray/cyeditor - A visual flow chart editor based on cytoscape.js. (Think Visio).

upsetjs/cytoscape.js-bubblesets

lukasy09/NeuralNetworkTool


Using Fetch

A basic fetch request is really simple to set up. Have a look at the following code:

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

JavaScript read JSON from URL


HTML

Best Practices In Modern Web Design: The Ultimate Round-Up
HTML <script> defer Attribute

Meta

HTTP headers

Viewport
Useful HTML Meta Tags


JavaScript

The Modern JavaScript Tutorial

Functions (MDN) (w3Schools) (javascript.info)

Arrow function expressions (MDN) (w3schools) (javascript.info)
Promise (MDN) (javascript.info) (pouchdb) (Promises/A+)
Promises chaining (MDN) (javascript.info) (medium.com)

image

Image (ref)
The future of JavaScript isn’t what it used to be


Papers:

A Technique for Drawing Directed Graphs (ref)
Visualisation of state machines using the Sugiyama
framework (ref)


JSON

Sites for testing JSON
Using JSON with SWI-Prolog

TypeScript

Type definitions for Cytoscape.js 3.1 (ref)

Online editor

CodeMirror - used by codepen.io

__

StackOverflow Cytoscape.js Q&A

What is the difference between D3.js and Cytoscape.js?
CytoscapeJS background color grey
I can’t get cytoscape.js to display my chart at all
Conditional styling for cytoscape
How to make cytoscape-ctxmenu.js applicable on nodes with condition?
Different behaviour for leaf nodes cytoscape
Show edge id on click
Find selected nodes/edges in cytoscape.js “on(‘unselect’)” event
Show dependet edge(s) when user click on node
How to draw an edge between two node in cytoscape with mouse drag drop
How do I reference ID as a variable in cytoscape.js
Cytoscape: show node/edge attributes in table when clicking on it
Set link behind background image and make it clickable Cytoscape.js
Add diffrenet href to some nodes in cytoscape.js
Rendering a family tree with d3 or cytoscape
How to listen to drag event of CytoscapeJS node (including only the one directly under the cursor/finger)
How to totally disable elements events?
Is there any example or sample code for the find and filter feature in Cytoscape JS
Is is possible to have a bidirectional edge in Cytoscape.js?
What javascript include files are needed to get layout extensions to work in cytoscape?
Use Algrebraic Operation in Cytoscape Selector Style Sheet
Cytoscape.js : right click event on nodes
see the list of event listeners currently attached
Filtering graph by connectivity to a specific node in cytoscape.js
Changing color when node is selected in Cytoscape JS
Cytoscape js how to get all edges (text label) when clicked on a node
CytoscapeJS background color grey
How to highlight neighbouring nodes in Cytoscape.js
How to highlight the path between two nodes in CYTOSCAPE JS
Content and Label on a Node - Cytoscape


When using SWI-Prolog dicts to represent JSON one may run into a few inconsistencies with converting back and forth between JSON and SWI-Prolog dicts. So to help me understand how to do it consistently I created a round robin predicate with examples.

Round robin predciate
:- module(json_to_prolog_dict,
    [
        example/1
    ]).

:- use_module(library(http/json)).

% operator: {}
% Basic structure AKA object.
%
% ?- example(1).
% {}
% "{}"
% true.
example(1) :-
    Dict = {},
    print_term(Dict,[]),
    nl,
    json_write_dict(current_output,Dict).

% operator: :
% Defines name-value paris.
% Note: {"key":"value"} is valid JSON
%       However {key:value} is not a valid SWI-Prolog dict. The dict requires a tag.
%
% ?- example(2).
% object{key:value}
% {"key":"value"}
% true.
example(2) :-
    Dict = object{ key:value },
    print_term(Dict,[]),
    nl,
    json_write_dict(current_output,Dict).

% operator: ,
% Combines same types.
%
% ?- example(3).
% object{key1:value1,key2:value2}
% {"key1":"value1", "key2":"value2"}
% true.
example(3) :-
    Dict = object{ key1:value1, key2:value2 },
    print_term(Dict,[]),
    nl,
    json_write_dict(current_output,Dict).

% []
% Array
% Note: Not allowed as a key
%
% ?- example(4).
% object{key:[]}
% {"key": []}
% true.
example(4) :-
    Dict = object{ key: [] },
    print_term(Dict,[]),
    nl,
    json_write_dict(current_output,Dict).

% Collection of objects using array.
%
% ?- example(5).
% collection{ objects:[ object{data:[]},
%                       object{data:[1]},
%                       object{data:[2,a]}
%                     ]
%           }
% {"objects": [ {"data": []},  {"data": [1 ]},  {"data": [2, "a" ]} ]}
% true.
example(5) :-
    Dict = collection{ objects: [object{ data: [] },object{ data: [1] },object{ data: [2,a] }] },
    print_term(Dict,[]),
    nl,
    json_write_dict(current_output,Dict).

% Note:
%   {} is not a dict however atom_json_dict/2 will correctly convert it to Json.
%  _{} is a dict.
example(20) :-
    Dict = _{},
    round_robin(_Json,Dict).

example(21) :-
    Json = {},
    round_robin(Json,_Dict).

example(22) :-
    Dict = _{ key:value },
    round_robin(_Json,Dict).

example(23) :-
    Json = { "key":"value" },
    round_robin(Json,_Dict).

example(24) :-
    Dict = _{ key1:value1, key2:value2 },
    round_robin(_Json,Dict).

example(25) :-
    Json = { "key1":"value1", "key2":"value2" },
    round_robin(Json,_Dict).

example(26) :-
    Dict = _{ key: [] },
    round_robin(_Json,Dict).

example(27) :-
    Json = { "key": [] },
    round_robin(Json,_Dict).

example(28) :-
    Dict = _{ objects: [_{ data: [] },_{ data: [1] },_{ data: [2,a] }] },
    round_robin(_Json,Dict).

example(29) :-
    Json = { "objects": [ { "data": [] }, { "data": ["1"] }, { "data": ["2","a"] }] },
    round_robin(Json,_Dict).

round_robin(Json0,Dict0) :-
    (
        var(Json0)
    ->
        % atom_json_dict/3 will accept some values that are not valid dicts, e.g. {}
        % Since the goal is to round robin the values, only valid dicts are allowed.
        is_dict(Dict0),
        atom_json_dict(Json0,Dict0,[]),
        atom_json_dict(Json0,Dict1,[value_string_as(atom)]),
        atom_json_dict(Json1,Dict1,[])
    ;
        % Since atom_json_dict/3 will not accept compounds, e.g. { "key":"value" }
        % need to convert input term into an atom.
        term_to_atom(Json0,Json_atom0),
        atom_json_dict(Json_atom0,Dict0,[]),
        atom_json_dict(Json_atom1,Dict0,[]),
        atom_json_dict(Json_atom1,Dict1,[]),
        % Since atom_json_dict/3 outputs atoms and some inputs are not atoms but terms,
        % need to covert atom into a term.
        % Since atoms and strings can be used interchangeably for most SWI-Prolog built-in predicate arguments
        % one can think of term_string/2 as term_atom/2.
        term_string(Json1,Json_atom1)
    ),
    % =@= used to allow for difference in system generated tags, e.g. _65108{} and _36929{} are the same.
    assertion( Dict0 =@= Dict1 ),
    assertion( Json0 == Json1 ).

Python implementation of visualization technique for (sklearn) decision trees. (ref)

2 Likes

@CapelliC

FYI

If you had not posted a reference to Cytoscape.js many months back I would not have been using it today. Thanks! :smiley:

Feel free to edit and add to the Wiki page. I don’t know how much more you know about using Cytoscape.js than me, but if you know much more, feel free to take the lead on this and rewrite the Wiki as you see fit or start a separate one.

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

Another example.

This one passes the data as JSON and not as JSON wrapped with a JavaScript variable. This is important because JSON is much easier to validate as input when being accepted for use on a web site. With SWI-Prolog the validation of the JSON can be done with DCGs and quasiquotations. (ref)

Uses
a. JavaScript promises.
b. JavaScript fetch

This has two buttons that will display data in the Internet browser console. F12 for Chrome, FireFox or Edge.

  1. Show the same JSON data used by cytoscpae()
  2. Demonstrates how to select values from the Cytoscape.js objects using JavaScript array map

This is just code for demonstration purposes; it has not been enhanced for production use.

Since this code uses JavaScript fetch, a server is required to pass the files to the Internet browser. If you are on Windows and install node.js you can start the server by clicking on http_server.lnk, then access the page as noted by the node.js startup messages.

Details - Click triangle to expand.

You will need to create http_server.lnk with the properties

Target: C:\Windows\System32\cmd.exe /k "http_server.bat"
Start in: Clear field so it is empty

http_server.bat

SET PATH=%ProgramFiles%\nodejs;%AppData%\npm
http-server --cors

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Cytoscape.js - Example - pass data as JSON</title>
        <link rel="stylesheet" href="style.css">
        <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
        <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="script.js" defer="defer"></script>
    </head>

    <body>
        <div id="cy"></div>
        <input type='button' id='showJson' value='Show JSON'>
        <input type='button' id='showPositions' value='Show Positions'>
    </body>

</html>

style.css

#cy {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0px;
    left: 0px;
}
#showJson {
    font-size: 40px;
    font-weight: bold;
    width: 10%;
    height: 5%;
    position: absolute;
    top: 5%;
    right: 80%;
}
#showPositions {
    font-size: 40px;
    font-weight: bold;
    width: 10%;
    height: 5%;
    position: absolute;
    top: 15%;
    right: 80%;
}

script.js

Promise.all([
    fetch('/elements.json'),
    fetch('/style.json'),
    fetch('/layout.json')
]).then(function (responses) {
    return Promise.all(responses.map(function (response) {
        return response.json();
    }));
}).then(data => {
    cy = cytoscape(
        {
            container: document.getElementById('cy'),
            elements: data[0],
            style: data[1],
            layout: data[2]
        }
    );
    return cy
}).then(cy => {
    let showJsonButton = document.getElementById('showJson');
    showJsonButton.addEventListener('click', function () {
        console.log('Elements:');
        console.log( cy.elements().jsons() );   // Gets all elements - JSON
        console.log('Styles:');
        console.log( cy.style().json() );       // Gets all styles - JSON
        console.log('Layout:');
        console.log(cy.options().layout);       // Gets layout
    });

    let showPositionsButton = document.getElementById('showPositions');
    showPositionsButton.addEventListener('click', function () {
        let positions = cy.nodes().map(node => node.position());
        console.log('Positions:');
        console.log(positions);
    });
}).catch(function (error) {
    console.log(error);
});

elements.json

{
    "nodes": [
        { "data": { "id": "a" } },
        { "data": { "id": "b" } }
    ],
    "edges": [
        { "data": { "source": "a", "target": "b" } }
    ]
}

style.json

[
    {
        "selector": "node",
        "style": {
            "label": "data(id)"
        }
    },
    {
        "selector": "edge",
        "style": {
            "curve-style": "haystack"
        }
    }
]

layout.json

{
    "name": "grid"
}
1 Like

A most simple single page JSON data exchange example, to be further expanded for interactivity.

/*  File:    cyajax.pl
    Author:  Carlo
    Created: Sep 13 2020
    Purpose: feeding CytoscapeJS with AJAX/JSON
*/

:- module(cyajax, []).

:- use_module(library(http/http_server)).
:- use_module(library(http/http_json)).
:- use_module(library(http/http_log)).
:- use_module(library(ugraphs)).

% when `make` just reload http://localhost:8081 to get changes
:- initialization (
       Port=8081,
       catch(http_stop_server(Port,[]),_,true),
       http_server([port(Port)])
   ).

:- http_handler(root(.),
                http_redirect(moved,location_by_id(home_page)),
                []).
:- http_handler(root(home),home_page,[]).

:- http_handler(root(run_cyajax),run_cyajax,[]).

home_page(_Request) :-
    reply_html_page(
        [ title('cyajax test'),
          \cy_cdn
        ],
        [ div([
            input([id=run_cyajax,type=button,value='run cyajax']),
            div([id=host_cy],'host cy')
          ]),
          \css,
          \js
        ]).

example_graph(1,
  [1-[2,3],2-[3,4,5],3-[1,2],4-[1,3],5-[4,5]]).

ugraph_to_cy_elements(G,Elements) :-
    vertices(G,Vs),
    maplist([V,_{data:_{id:V}}]>>true,Vs,Nodes),
    edges(G,Es),
    maplist([Source-Target,
             _{data:_{id:EdgeId,source:Source,target:Target}}
            ]>>format(string(EdgeId),"~w->~w",[Source,Target])
            ,Es,Edges),
    append(Nodes,Edges,Elements).

run_cyajax(Request) :-
  http_read_json_dict(Request,Dict),
  example_graph(Dict.example,G),
  ugraph_to_cy_elements(G,Es),
  reply_json_dict(_{elements:Es,layout:cose}).

cy_cdn -->
    html(script([src=
      'https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.15.2/cytoscape.min.js'
    ],'')).

css --> html(style('

#host_cy {
  height: 400px;
  width: 400px;
  background-color: lime;
}

')).

js --> html(script("

window.onload = () => {

  const request = (url,json) => fetch(url,{
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(json)
  }).then(r => r.json())

  run_cyajax.onclick = async (ev) => {
    try {
      render_cy(await request('/run_cyajax', {example:1}))
    } catch(e) {
      alert(e.message)
    }
  }

  render_cy = (from_pl) => {
    var cy = {
        container: host_cy,
        style: [
            {   selector: 'node',
                style: {
                    'background-color': '#666',
                    'label': 'data(id)'
                }
            },
            {   selector: 'edge',
                style: {
                    'target-arrow-shape': 'triangle',
                    'width': 3,
                    'line-color': '#ddd',
                    'target-arrow-color': '#ddd',
                    'curve-style': 'bezier'
                }
            }
        ],
    }
    if (from_pl.elements)
      cy.elements = from_pl.elements
    cytoscape(cy).makeLayout({ name: from_pl.layout }).run()
  }
}

")).
2 Likes

Nice!

Result of running cyajax.pl by Carlo in this post

While learning how to use ChatGPT with coding created a better base set of code and files for my Cytoscape.js with SWI-Prolog example.

The code is not in an expandable text section as that hides it from search indexing and the code needs to be found via a search.

Note: The directory C:/Users/Groot is just an example you can choose another directory just make sure that files can be created and written in the directory by the user.

This examples needs an HTML server. To start the server:

Welcome to SWI-Prolog (threaded, 64 bits, version 8.5.15)
...

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

?- [server].
% Started server at http://localhost:8080/
true.

Then using an internet browser: http://localhost:8080/london_tube.html


Here are the needed files.

File: server.pl

:- module(server, [start/0]).

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

:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).

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

% Start the server when the code is loaded.
:- initialization(start).

% Start the server on port 8080
start :-
    http_server(http_dispatch, [port(8080)]).

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

%               URL                                File name
:- http_handler('/london_tube.html', http_reply_file('london_tube.html', []), []).
:- http_handler('/elements.json'   , http_reply_file('elements.json',    []), []).
:- http_handler('/style.json'      , http_reply_file('style.json',       []), []).
:- http_handler('/layout.json'     , http_reply_file('layout.json',      []), []).
:- http_handler('/script.js'       , http_reply_file('script.js',        []), []).
:- http_handler('/style.css'       , http_reply_file('style.css',        []), []).
:- http_handler('/favicon.ico'     , http_reply_file('favicon.ico',      []), []).

File: london_tube.html

<html>
<head>
  <link rel="icon" type="image/x-icon" href="./favicon.ico">
  <link rel="stylesheet" href="style.css">
  <script src="https://cdn.jsdelivr.net/npm/cytoscape/dist/cytoscape.min.js"></script>
  <script src="script.js"></script>
</head>
<body>
  <!-- final -->
  <div id="cy"></div>
</body>
</html>

File: script.js

async function initCytoscape() {
  try {
    const elementsPromise = fetch("elements.json")
      .then(res => {
        if (!res.ok) {
          throw new Error(`Unable to fetch elements.json: ${res.status} ${res.statusText}`);
        }
        return res.json();
      })
      .catch(error => {
        console.error(error);
      });

    const stylePromise = fetch("style.json")
      .then(res => {
        if (!res.ok) {
          throw new Error(`Unable to fetch style.json: ${res.status} ${res.statusText}`);
        }
        return res.json();
      })
      .catch(error => {
        console.error(error);
      });

    const layoutPromise = fetch("layout.json")
      .then(res => {
        if (!res.ok) {
          throw new Error(`Unable to fetch layout.json: ${res.status} ${res.statusText}`);
        }
        return res.json();
      })
      .catch(error => {
        console.error(error);
      });

    const [elements, style, layout] = await Promise.all([elementsPromise, stylePromise, layoutPromise]);

    var cy = cytoscape({
      container: document.getElementById("cy"),
      elements: elements,
      style: style,
      layout: layout
    });
  } catch (error) {
    console.error(error);
  }
}

initCytoscape();

File: style.css

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

File: favicon.ico
Just copy one here. There is one installed for SWI-Prolog that works if you need one.

There are three JSON files used with Cytoscape.js and are created using london_tube.pl and cytoscape.pl.

To create the three JSON files

Welcome to SWI-Prolog (threaded, 64 bits, version 8.5.15)
...

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

?- [london_tube].
true.

?- halt.

File: london_tube.pl

:- module(london_tube,[]).

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

:- prolog_load_context(directory, Dir),
asserta(user:file_search_path(myapp, Dir)).

user:file_search_path(data,myapp('Data')).

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

:- use_module(myapp(cytoscape)).

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

% Using the London Tube Map from: https://cdn.londonandpartners.com/-/media/files/london/visit/maps-and-guides/tube-maps/tube_map_november_2022.pdf?la=en

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

% Automatically run the code. No need to load the code then run a query.
:- initialization(main).

main :-
    % Convert the line/3 predicates into connection/2 predicates.
    forall(line(name(Line), _, _), makeConnections(Line)),
    create_json_files.

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

create_json_files :-
    absolute_file_name(myapp(elements), Elements_path, [extensions([json]), access(write)]),
    write_elements_json(nodes,edges,Elements_path),
    absolute_file_name(myapp(style), Style_path, [extensions([json]), access(write)]),
    write_style_json(graph_style,Style_path),
    absolute_file_name(myapp(layout), Layout_path, [extensions([json]), access(write)]),
    write_layout_json(graph_layout,Layout_path).

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

line(name('Elizabeth'),color('Purple'),stations(['Bond Street','Tottenham Court Road'])).
line(name('Central'),color('Red'),stations(['Bond Street','Oxford Circus','Tottenham Court Road','Holborn'])).
line(name('Piccadilly'),color('Dark Blue'),stations(['Hyde Park Corner','Green Park','Piccadilly Circus','Leicester Square','Covent Garden','Holborn'])).
line(name('Circle'),color('Yellow'),stations(['Sloane Square','Victoria','St James\'s Park','Westminster','Embankment','Temple'])).
line(name('District'),color('Green'),stations(['Sloane Square','Victoria','St James\'s Park','Westminster','Embankment','Temple'])).
line(name('Jubilee'),color('Gray'),stations(['Bond Street','Green Park','Westminster'])).
line(name('Bakerloo'),color('Brown'),stations(['Regent\'s Park','Oxford Circus','Piccadilly Circus','Charing Cross','Embankment'])).
line(name('Victoria'),color('Light Blue'),stations(['Oxford Circus','Green Park','Victoria'])).
line(name('Northern'),color('Black'),stations(['Goddge Street','Tottenham Court Road','Leicester Square','Charing Cross','Embankment'])).

makeConnections(Line) :-
    line(name(Line), _, stations(Stations)),
    makeConnections(Stations).

:- dynamic connection/2.

makeConnections([_]).
makeConnections([Start,End|Rest]) :-
    (
        connection(Start,End), !
    ;
        assert(connection(Start, End))
    ),
    makeConnections([End|Rest]).

% -------

% https://js.cytoscape.org/#notation/elements-json

nodes(Nodes) :-
    findall(node(X), (connection(X, _); connection(_, X)), TempNodes),
    sort(TempNodes, Nodes).

edges(Edges) :-
    findall(edge(X, Y), connection(X, Y), TempEdges),
    sort(TempEdges, Edges).

% -------

% https://js.cytoscape.org/#style

% Note:
%   graph_style(selector(node),property('label'           , 'data(id)' )).
%   added automatically.
graph_style(selector(node),property('background-color', '#666'     )).
graph_style(selector(edge),property('curve-style'     , haystack   )).
graph_style(selector(edge),property('line-color'      , '#ccc'     )).

% -------

% https://js.cytoscape.org/#layouts

graph_layout(property('name',grid)).

File: cytoscape.pl

:- module(cytoscape,
    [
        write_elements_json/3,
        write_style_json/2,
        write_layout_json/2
    ]).

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

:- use_module(library(http/json)).
:- use_module(library(dict)).

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

% https://js.cytoscape.org/#notation/elements-json

node_properties_dict(node(Name), Dict) :-
    dict_create(Dict, _, ['id'-Name]).

node_dict(Name, Dict) :-
    node_properties_dict(Name, Node_properties),
    dict_create(Dict, _, ['data'-Node_properties]).

edge_properties_dict(edge(Source, Target), Dict) :-
    dict_create(Dict, _, ['source'-Source, 'target'-Target]).

edge_dict(Edge, Dict) :-
    edge_properties_dict(Edge, Edge_properties),
    dict_create(Dict, _, ['data'-Edge_properties]).

nodes_dict(Nodes_goal,Nodes_dict) :-
    call(Nodes_goal,Nodes),
    maplist(node_dict,Nodes,Node_dicts),
    dict_create(Nodes_dict,_,['nodes'-Node_dicts]).

edges_dict(Edges_goal,Edges_dict) :-
    call(Edges_goal,Edges),
    maplist(edge_dict,Edges,Edge_dicts),
    dict_create(Edges_dict,_,['edges'-Edge_dicts]).

elements_dict(Nodes_goal,Edges_goal,Elements_dict) :-
    nodes_dict(Nodes_goal,Nodes_dict),
    edges_dict(Edges_goal,Edges_dict),
    put_dict(Nodes_dict,Edges_dict,Elements_dict).

:- meta_predicate write_elements_json(:,:,?).

write_elements_json(Nodes_goal,Edges_goal,Path) :-
    elements_dict(Nodes_goal,Edges_goal,Elements),
    setup_call_cleanup(
        open(Path, write, Stream, [encoding(utf8)]),
        json_write_dict(Stream, Elements),
        close(Stream)
    ).

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

% https://js.cytoscape.org/#style

style_dicts(M:_,[Dict1,Dict3]) :-
    Node_properties = [label-'data(id)'|T1],
    findall(Key-Value,( M:graph_style(selector(node),property(Key,Value)) ),T1,[]),
    dict_create(Dict0,_,Node_properties),
    dict_create(Dict1,_,[selector-node,style-Dict0]),
    findall(Key-Value,( M:graph_style(selector(edge),property(Key,Value)) ),Edge_properties),
    dict_create(Dict2,_,Edge_properties),
    dict_create(Dict3,_,[selector-edge,style-Dict2]).

:- meta_predicate write_style_json(:,?).

write_style_json(Goal,Path) :-
    style_dicts(Goal,Styles),
    setup_call_cleanup(
        open(Path, write, Stream, [encoding(utf8)]),
        json_write_dict(Stream, Styles),
        close(Stream)
    ).

% -------

% https://js.cytoscape.org/#layouts

layout_dicts(M:_,Dict) :-
    findall(Key-Value,( M:graph_layout(property(Key,Value)) ),Layout_properties),
    dict_create(Dict,_,Layout_properties).

:- meta_predicate write_layout_json(:,?).

write_layout_json(Goal,Path) :-
    layout_dicts(Goal,Layout),
    setup_call_cleanup(
        open(Path, write, Stream, [encoding(utf8)]),
        json_write_dict(Stream,Layout),
        close(Stream)
    ).

Versions of the JSON files, if needed.

File: elements.json

{
  "edges": [
    {"data": {"source":"Bond Street", "target":"Green Park"}},
    {"data": {"source":"Bond Street", "target":"Oxford Circus"}},
    {
      "data": {"source":"Bond Street", "target":"Tottenham Court Road"}
    },
    {"data": {"source":"Charing Cross", "target":"Embankment"}},
    {"data": {"source":"Covent Garden", "target":"Holborn"}},
    {"data": {"source":"Embankment", "target":"Temple"}},
    {
      "data": {"source":"Goddge Street", "target":"Tottenham Court Road"}
    },
    {"data": {"source":"Green Park", "target":"Piccadilly Circus"}},
    {"data": {"source":"Green Park", "target":"Victoria"}},
    {"data": {"source":"Green Park", "target":"Westminster"}},
    {"data": {"source":"Hyde Park Corner", "target":"Green Park"}},
    {"data": {"source":"Leicester Square", "target":"Charing Cross"}},
    {"data": {"source":"Leicester Square", "target":"Covent Garden"}},
    {"data": {"source":"Oxford Circus", "target":"Green Park"}},
    {"data": {"source":"Oxford Circus", "target":"Piccadilly Circus"}},
    {
      "data": {"source":"Oxford Circus", "target":"Tottenham Court Road"}
    },
    {"data": {"source":"Piccadilly Circus", "target":"Charing Cross"}},
    {
      "data": {"source":"Piccadilly Circus", "target":"Leicester Square"}
    },
    {"data": {"source":"Regent's Park", "target":"Oxford Circus"}},
    {"data": {"source":"Sloane Square", "target":"Victoria"}},
    {"data": {"source":"St James's Park", "target":"Westminster"}},
    {"data": {"source":"Tottenham Court Road", "target":"Holborn"}},
    {
      "data": {"source":"Tottenham Court Road", "target":"Leicester Square"}
    },
    {"data": {"source":"Victoria", "target":"St James's Park"}},
    {"data": {"source":"Westminster", "target":"Embankment"}}
  ],
  "nodes": [
    {"data": {"id":"Bond Street"}},
    {"data": {"id":"Charing Cross"}},
    {"data": {"id":"Covent Garden"}},
    {"data": {"id":"Embankment"}},
    {"data": {"id":"Goddge Street"}},
    {"data": {"id":"Green Park"}},
    {"data": {"id":"Holborn"}},
    {"data": {"id":"Hyde Park Corner"}},
    {"data": {"id":"Leicester Square"}},
    {"data": {"id":"Oxford Circus"}},
    {"data": {"id":"Piccadilly Circus"}},
    {"data": {"id":"Regent's Park"}},
    {"data": {"id":"Sloane Square"}},
    {"data": {"id":"St James's Park"}},
    {"data": {"id":"Temple"}},
    {"data": {"id":"Tottenham Court Road"}},
    {"data": {"id":"Victoria"}},
    {"data": {"id":"Westminster"}}
  ]
}

File: style.json

[
  {
    "selector":"node",
    "style": {"background-color":"#666", "label":"data(id)"}
  },
  {
    "selector":"edge",
    "style": {"curve-style":"haystack", "line-color":"#ccc"}
  }
]

File: layout.json

{"name":"grid"}

If these errors are encountered on Windows

Exported procedure socket: tcp_host_to_address/2 is not defined
Exported procedure socket: tcp_connect/2 is not defined

Do an uninstall then clean install of SWI-Prolog

  • Run Uninstall.exe from the SWI-Prolog directory, e.g. C:\Program Files\swipl
  • Install SWI-Prolog again.

Don’t know why this worked but it did.