This is a topic to discuss the Wiki
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.
- Download and install node.js
Download | Node.js - Install http-server package
npm install --global http-server
- Change to directory that is the root of the files for the web site
- 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
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
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)
- array of collections
- 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
- Create a button in the HTML body tag
<input type='button' id='advance' value='Export'>
- 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();
});
}
- 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
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));
HTML
Best Practices In Modern Web Design: The Ultimate Round-Up
HTML <script> defer Attribute
Meta
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 (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)
FYI
If you had not posted a reference to Cytoscape.js many months back I would not have been using it today. Thanks!
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.
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.
- Show the same JSON data used by cytoscpae()
- 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"
}
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()
}
}
")).
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.