SPARQL to JSON
The results of these queries are manually transformed into Ogma's RawGraph format.
The loading can be slow at first due to a double query run and wikidata availability.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../build/ogma.min.js"></script>
<link href="fonts/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.19.0/themes/prism.min.css"
integrity="sha256-cuvic28gVvjQIo3Q4hnRpQSNB0aMw3C+kjkR0i+hrWg=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.19.0/prism.min.js"
integrity="sha256-YZQM6/hLBZYkb01VYf17isoQM4qpaOP+aX96hhYrWhg=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.19.0/plugins/autoloader/prism-autoloader.min.js"
integrity="sha256-WIuEtgHNTdrDT2obGtHYz/emxxAj04sJBdMhRjDXd8I=" crossorigin="anonymous"></script>
<style>
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
#info {
position: absolute;
color: #fff;
background: #141229;
font-size: 12px;
font-family: monospace;
padding: 5px;
top: 0;
left: 0;
white-space: pre;
}
.toolbar {
display: block;
position: absolute;
top: 20px;
right: 20px;
padding: 10px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
border-radius: 4px;
background: #ffffff;
color: #222222;
font-weight: 300;
max-width: 350px;
}
.toolbar .section {
position: relative;
display: block;
}
.toolbar h3 {
display: block;
font-weight: 300;
border-bottom: 1px solid #ddd;
color: #606060;
font-size: 1rem;
font-family: Arial, Helvetica, sans-serif;
}
.toolbar img {
margin-top: 15px;
float: right;
}
.controls {
text-align: center;
margin-top: 5px;
}
.btn {
padding: 6px 8px;
background-color: white;
cursor: pointer;
font-size: 18px;
border: none;
border-radius: 5px;
outline: none;
}
.btn:hover {
color: #333;
background-color: #e6e6e6;
}
.menu {
border: 1px solid #ddd;
width: 80%;
font-size: 14px;
margin-top: 10px;
}
</style>
</head>
<body>
<div id="graph-container"></div>
<div class="toolbar">
<h3>Last wikidata SPARQL query </h3>
<pre><code class="language-sparql" id="query-text"></code></pre>
<div class="controls">
<button id="layout" class="btn menu">Layout</button>
</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/36/Wikidata_stamp_rec_light.png"
alt="powered by Wikidata" width="100" />
</div>
<div id="info">loading...</div>
<script>
'use strict';
var ogma = new Ogma({
container: 'graph-container',
});
var MovieType = 'Movie';
var DirectorType = 'Director';
var ActorType = 'Actor';
// some UI logic
function toggleLoading() {
var el = document.querySelector('#info');
var enable = el.style.display;
el.style.display = enable === 'block' ? 'none' : 'block';
}
function updateSPARQLQuery(query) {
var codeBlock = document.querySelector('#query-text');
codeBlock.innerHTML = query;
Prism.highlightElement(codeBlock);
}
// Query templates
function getStartTemplate() {
return [
'SELECT DISTINCT ?movie ?movieLabel WHERE {',
'?movie wdt:P31 wd:Q11424.',
'?movie wdt:P1476 "The Matrix"@en.',
'SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }',
'}',
'LIMIT 1'
].join('\n');
}
function getMovieExpandQuery(id) {
return [
'SELECT DISTINCT ?actor ?actorLabel ?director ?directorLabel WHERE {',
'wd:' + id + ' wdt:P161 ?actor.',
'wd:' + id + ' wdt:P57 ?director.',
'SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }',
'}'
].join('\n');
}
function getPersonExpandQuery(id) {
return [
'SELECT DISTINCT ?movie ?movieLabel WHERE {',
'?movie wdt:P161|wdt:P57 wd:' + id + '.',
'SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }',
'}'
].join('\n');
}
function getExpandQuery(id, type) {
if (type === MovieType) {
return getMovieExpandQuery(id);
}
return getPersonExpandQuery(id);
}
// Parsing logic
function parseMovieResults(data) {
// the triple format is [movie: id, movieLabel: title]
var entities = data.results.bindings;
return entities.map(function (node) {
return {
id: getCleanId(node.movie.value),
data: { type: MovieType, title: node.movieLabel.value }
};
});
}
function getCleanId(uri) {
return uri.replace('http://www.wikidata.org/entity/', '')
}
function parsePersonResults(data) {
var nodes = [];
// the triple format is: [actorId, actor, actorGenre, directorId, director, directorGenre]
var entities = data.results.bindings;
// each record contains more than one node information
entities.forEach(function (node) {
nodes.push({ id: getCleanId(node.actor.value), data: { type: ActorType, name: node.actorLabel.value } });
nodes.push({ id: getCleanId(node.director.value), data: { type: DirectorType, name: node.directorLabel.value } });
});
return nodes;
}
function getEdgeType(sourceType, targetType) {
if (sourceType === MovieType) {
return targetType === DirectorType ? 'DIRECTED' : 'ACTED_IN';
}
return sourceType === DirectorType ? 'DIRECTED' : 'ACTED_IN';
}
function parseSPARQL(data, source, type) {
// initial query or we're expanding a person node, so result will be of movie type
var isMovieNodesResults = !type || type !== MovieType;
var graph = {
nodes: isMovieNodesResults ? parseMovieResults(data) : parsePersonResults(data),
edges: []
};
// first query has no source
if (source) {
var sourceType = ogma.getNode(source).getData('type');
graph.edges = graph.nodes.map(function (target) {
var type = getEdgeType(sourceType, target.data.type);
return { id: source + '-' + target.id, source, target: target.id, data: { type } }
})
}
return graph;
}
// Fetch data from Wikidata via the SPARQL endpoint
function sendQuery(id, type) {
var query = id ? getExpandQuery(id, type) : getStartTemplate();
toggleLoading();
updateSPARQLQuery(query);
return fetch('https://query.wikidata.org/sparql?query=' + encodeURIComponent(query), {
headers: {
'Accept': 'application/sparql-results+json'
}
})
.then(function (response) {
return response.json();
})
.then(function (result) {
return parseSPARQL(result, id, type);
})
.then(function (graph) {
toggleLoading();
return ogma.addGraph(graph);
})
.then(function () {
// if it's the first query run another expand
if (ogma.getNodes().size === 1) {
return sendQuery(ogma.getNodes().getId()[0], MovieType)
}
return layout();
});
}
function layout() {
return ogma.layouts.force({
locate: { padding: 80 },
});
}
// Define what to use as node and edge captions.
ogma.styles.addEdgeRule({
text: {
content: function (e) {
return e.getData('type');
}
},
color: function (e) {
var type = e.getData('type');
if (type === ('ACTED_IN')) {
return '#22b455';
}
if (type === ('DIRECTED')) {
return '#80ce87';
}
}
});
ogma.styles.addNodeRule({
text: {
content: function (n) {
if (n.getData('type') === MovieType) {
return n.getData('title');
}
if (n.getData('type') === DirectorType || n.getData('type') === ActorType) {
return n.getData('name');
}
}
},
icon: {
content: function (n) {
if (n.getData('type') === MovieType) {
return "\uf008";
}
if (n.getData('type') === DirectorType || n.getData('type') === ActorType) {
return "\uf007";
}
},
font: 'FontAwesome',
color: 'black',
minVisibleSize: 0
},
outerStroke: {
color: '#204829',
width: 2
},
color: 'white'
});
// Expand on double click
ogma.events.onDoubleClick(function (evt) {
if (evt.target && evt.target.isNode) {
sendQuery(evt.target.getId(), evt.target.getData('type'));
}
});
document.querySelector('#layout').addEventListener('click', function (evt) {
evt.preventDefault();
layout();
});
sendQuery();
</script>
</body>
</html>