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.