All versions of this documentation
X

SPARQL to JSON

In this demo it is shown how to use the Wikidata SPARQL endpoint to fetch data about Movies.
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.

Open in a new window.
          <!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>