OG
JS
Docs
Demos
Examples
Database result lab
Convert backend query results into one graph shape. Credentials and drivers stay on your server.
Neo4j
Gremlin
SPARQL
SQL rows
// Database drivers and credentials stay on the server; this page starts with returned rows. // The selected adapter converts backend-specific records into OGJS graph data, after which // layout, rendering, interaction, and framing use the same public client-side path. import { Ogjs, gremlinToGraph, neo4jToGraph, rowsToGraph, sparqlToGraph, } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light' }); // These fixtures mirror the values a backend would serialize after running each query. // They deliberately keep the native database shapes so the example shows where conversion // belongs instead of pretending every driver already returns OGJS node and edge records. const neo4j = [{ alice: { elementId: 'a', labels: ['Person'], properties: { name: 'Alice' } }, work: { elementId: 'r1', type: 'KNOWS', startNodeElementId: 'a', endNodeElementId: 'b', properties: { channel: 'work' }, }, bob: { elementId: 'b', labels: ['Person'], properties: { name: 'Bob' } }, school: { elementId: 'r2', type: 'KNOWS', startNodeElementId: 'a', endNodeElementId: 'b', properties: { channel: 'school' }, }, transfer: { elementId: 'r3', type: 'SENT', startNodeElementId: 'b', endNodeElementId: 'c', properties: { amount: 240 }, }, carol: { elementId: 'c', labels: ['Person'], properties: { name: 'Carol' } }, }]; const gremlin = [ { id: 'm', label: 'person', name: 'Marko' }, { id: 'v', label: 'person', name: 'Vadas' }, { id: 'l', label: 'software', name: 'Lop' }, { id: 'e1', label: 'knows', OUT: { id: 'm' }, IN: { id: 'v' }, weight: 0.5 }, { id: 'e2', label: 'created', OUT: { id: 'm' }, IN: { id: 'l' }, weight: 0.4 }, ]; const sparql = { results: { bindings: [ { s: { value: 'https://example.test/Alice' }, p: { value: 'https://example.test/knows' }, o: { value: 'https://example.test/Bob' }, }, { s: { value: 'https://example.test/Bob' }, p: { value: 'https://example.test/worksWith' }, o: { value: 'https://example.test/Carol' }, }, ] } }; const rows = [ { source_id: 1, target_id: 2, source_name: 'Acme', target_name: 'Subsidiary', relation: 'OWNS', share: 80 }, { source_id: 1, target_id: 3, source_name: 'Acme', target_name: 'Supplier', relation: 'OWNS', share: 25 }, ]; const payloads = { neo4j, gremlin, sparql, rows }; const colors = { neo4j: ['#2563eb', '#7c3aed', '#059669'], gremlin: ['#ea580c', '#2563eb', '#059669'], sparql: ['#7c3aed', '#2563eb', '#dc2626'], rows: ['#0f172a', '#2563eb', '#059669'], }; function convert(format) { // Each public adapter removes one database-specific concern and returns the same graph // contract. For SQL rows, the field map names which columns identify endpoints, labels, // and relationship types because tabular results do not carry that meaning themselves. if (format === 'neo4j') return neo4jToGraph(neo4j); if (format === 'gremlin') return gremlinToGraph(gremlin); if (format === 'sparql') return sparqlToGraph(sparql); return rowsToGraph(rows, { source: 'source_id', target: 'target_id', edgeType: 'relation', sourceLabel: 'source_name', targetLabel: 'target_name', }); } function relationOf(format, graph) { // Adapter outputs keep source-specific properties in `edge.data`. Reading one property // here demonstrates that normalization does not discard the evidence returned by a query. const data = graph.edges[0]?.data ?? {}; if (format === 'neo4j') return data.channel; if (format === 'gremlin') return data.label; if (format === 'sparql') return data.predicate; return data.type; } function show(format) { const graph = convert(format); // The adapter output contains identity and domain data. We add only semantic styling here; // OGJS owns placement so every database format follows the same layout path. graph.nodes = graph.nodes.map((node, index) => ({ ...node, size: index === 0 ? 14 : 10, color: colors[format][index], })); // From this boundary onward the rendering path is database-agnostic: one `setGraph` call // replaces the visible dataset, and camera fitting leaves room for the application panel. og.setGraph(graph); og.layouts.circular({ radius: 92 }); og.camera.fit({ target: 'all', avoid: document.querySelector('.workflow-panel'), paddingPx: 24, maxOccupancy: .48, }); const counts = `${graph.nodes.length} nodes · ${graph.edges.length} edges`; document.querySelector('#database-status').textContent = graph.nodes.length ? `${format.toUpperCase()} · ${counts}` : 'No graph records returned'; document.querySelector('#database-preview').textContent = JSON.stringify(payloads[format], null, 2).split('\n').slice(0, 14).join('\n'); document.querySelectorAll('[data-database]').forEach((button) => button.setAttribute('aria-pressed', String(button.dataset.database === format))); } document.querySelectorAll('[data-database]').forEach((button) => button.addEventListener('click', () => show(button.dataset.database))); show('neo4j');