Skip to content

Commit 932c8d0

Browse files
authored
Merge pull request #2 from Matdata-eu/001-construct-graph-viz
001 construct graph viz
2 parents 63322c8 + de05713 commit 932c8d0

File tree

17 files changed

+170
-321
lines changed

17 files changed

+170
-321
lines changed

.github/workflows/deploy-demo.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Deploy Demo to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: "pages"
16+
cancel-in-progress: false
17+
18+
jobs:
19+
deploy:
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: '20'
32+
cache: 'npm'
33+
34+
- name: Install dependencies
35+
run: npm ci
36+
37+
- name: Build plugin
38+
run: npm run build
39+
40+
- name: Setup Pages
41+
uses: actions/configure-pages@v4
42+
43+
- name: Upload artifact
44+
uses: actions/upload-pages-artifact@v3
45+
with:
46+
path: './demo'
47+
48+
- name: Deploy to GitHub Pages
49+
id: deployment
50+
uses: actions/deploy-pages@v4

README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
44
[![npm version](https://img.shields.io/npm/v/yasgui-graph-plugin.svg)](https://www.npmjs.com/package/yasgui-graph-plugin)
55

6-
A YASGUI plugin for visualizing SPARQL CONSTRUCT query results as interactive graphs with nodes (subjects/objects) and edges (predicates).
6+
A YASGUI plugin for visualizing SPARQL CONSTRUCT and DESCRIBE query results as interactive graphs with nodes (subjects/objects) and edges (predicates).
77

88
## ✨ Features
99

1010
- **🔷 Interactive Graph Visualization**: Automatic force-directed layout with smooth physics-based positioning
1111
- **🎨 Smart Color Coding**:
12-
- 🔵 Blue = URIs
13-
- 🟢 Green = rdf:type objects (classes)
14-
- 🔘 Grey = Literals
15-
- 🟡 Yellow = Blank nodes
12+
- 🔵 Light Blue (#97C2FC) = URIs
13+
- 🟢 Light Green (#a6c8a6ff) = rdf:type objects (classes)
14+
- ⚪ Light Grey (#c5c5c5ff) = Literals
15+
- 🟠 Orange (#e15b13ff) = Blank nodes
1616
- **🔍 Navigation**: Mouse wheel zoom, drag to pan, "Zoom to Fit" button
1717
- **✋ Drag & Drop**: Reorganize nodes by dragging them to new positions
1818
- **💬 Tooltips**: Hover for full URI/literal details (300ms delay)
@@ -66,8 +66,9 @@ const yasgui = new Yasgui(document.getElementById('yasgui'), {
6666
});
6767
```
6868

69-
### Sample CONSTRUCT Query
69+
### Sample Queries
7070

71+
**CONSTRUCT Query:**
7172
```sparql
7273
PREFIX ex: <http://example.org/>
7374
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
@@ -82,6 +83,14 @@ CONSTRUCT {
8283
WHERE {}
8384
```
8485

86+
**DESCRIBE Query:**
87+
```sparql
88+
PREFIX ex: <http://example.org/>
89+
90+
# Returns all triples about the specified resources
91+
DESCRIBE ex:Alice ex:Bob
92+
```
93+
8594
After running the query, click the **"Graph"** tab to see the visualization.
8695

8796
## 🎮 User Guide
@@ -99,10 +108,10 @@ After running the query, click the **"Graph"** tab to see the visualization.
99108

100109
| Color | Meaning | Example |
101110
|-------|---------|---------|
102-
| 🔵 Blue | URI nodes | `ex:Person`, `ex:Alice` |
103-
| 🟢 Green | rdf:type objects (classes) | `ex:Person` in `ex:Alice rdf:type ex:Person` |
104-
| 🔘 Grey | Literal values | `"Alice"`, `"30"^^xsd:integer` |
105-
| 🟡 Yellow | Blank nodes | `_:b1`, `_:addr1` |
111+
| 🔵 Light Blue (#97C2FC) | URI nodes | `ex:Person`, `ex:Alice` |
112+
| 🟢 Light Green (#a6c8a6ff) | rdf:type objects (classes) | `ex:Person` in `ex:Alice rdf:type ex:Person` |
113+
| ⚪ Light Grey (#c5c5c5ff) | Literal values | `"Alice"`, `"30"^^xsd:integer` |
114+
| 🟠 Orange (#e15b13ff) | Blank nodes | `_:b1`, `_:addr1` |
106115

107116
## ⚙️ Configuration
108117

@@ -213,17 +222,18 @@ Contributions welcome! Please follow the project constitution (`.specify/memory/
213222
## 🐛 Troubleshooting
214223

215224
### Plugin tab not showing
216-
- Ensure query type is **CONSTRUCT** (not SELECT/ASK/DESCRIBE)
225+
- Verify plugin is registered correctly
217226
- Check browser console for errors
218227
- Verify YASGUI version is ^4.0.0
219228

220229
### Empty visualization
221-
- Confirm CONSTRUCT query returns triples (check "Table" or "Response" tab)
222-
- Verify results have subject/predicate/object structure
230+
- Ensure query type is **CONSTRUCT** or **DESCRIBE**
231+
- Confirm query returns triples (check "Table" or "Response" tab)
232+
- Verify results have RDF structure
223233

224234
### Performance issues
225235
- Limit results to <1000 nodes for best performance
226-
- Disable physics after initial layout (automatic)
236+
- Disable physics after initial layout
227237
- Consider using LIMIT clause in SPARQL query
228238

229239
For more help, see [Quickstart Guide - Troubleshooting](./specs/001-construct-graph-viz/quickstart.md#troubleshooting).

demo/index.html

Lines changed: 26 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<title>YASGUI Graph Plugin Demo</title>
1515

1616
<!-- YASGUI CSS -->
17-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@zazuko/yasgui@4/build/yasgui.min.css">
17+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@zazuko/yasgui@4.6.1/build/yasgui.min.css">
1818

1919
<!-- Font Awesome for icons -->
2020
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
@@ -111,12 +111,11 @@ <h2 onclick="toggleInstructions()">
111111
</h2>
112112
<div class="instructions-content collapsed">
113113
<ul>
114-
<li>Click the <strong>"Graph"</strong> tab after running a CONSTRUCT query</li>
115-
<li><strong>Sample queries</strong> are pre-loaded (see tabs: Basic, Ontology, Blank Nodes, Multiple Edges)</li>
114+
<li>Click the <strong>"Graph"</strong> tab after running a CONSTRUCT or DESCRIBE query</li>
115+
<li><strong>Sample queries</strong> are pre-loaded (see tabs: Basic, Ontology, Blank Nodes, Multiple Edges, DESCRIBE)</li>
116116
<li><strong>Navigate</strong>: Mouse wheel to zoom, drag background to pan, click "Zoom to Fit" button</li>
117117
<li><strong>Reorganize</strong>: Drag individual nodes to rearrange the layout</li>
118118
<li><strong>Inspect</strong>: Hover over nodes/edges to see full details (300ms delay)</li>
119-
<li><strong>Export</strong>: Click "Export PNG" to save the current viewport as an image</li>
120119
<li><strong>Color coding</strong>: <span style="color: #c5c5c5ff">⬤ Grey</span> = literals,
121120
<span style="color: #e15b13ff">⬤ Orange</span> = blank nodes,
122121
<span style="color: #a6c8a6ff">⬤ Green</span> = rdf:type objects,
@@ -137,7 +136,7 @@ <h2 onclick="toggleInstructions()">
137136
<div id="yasgui"></div>
138137

139138
<!-- YASGUI JavaScript -->
140-
<script src="https://cdn.jsdelivr.net/npm/@zazuko/yasgui@4/build/yasgui.min.js"></script>
139+
<script src="https://cdn.jsdelivr.net/npm/@zazuko/yasgui@4.6.1/build/yasgui.min.js"></script>
141140

142141
<!-- vis-network (required dependency) -->
143142
<script src="https://unpkg.com/[email protected]/standalone/umd/vis-network.min.js"></script>
@@ -159,87 +158,36 @@ <h2 onclick="toggleInstructions()">
159158

160159
// Initialize YASGUI after plugin is registered
161160
const yasgui = new Yasgui(document.getElementById('yasgui'), {
162-
endpoint: 'http://localhost:3030/g',
161+
// Set the SPARQL endpoint
162+
requestConfig: {
163+
endpoint: 'http://localhost:3030/g',
164+
},
165+
yasr: {
166+
pluginOrder: ['Table', 'Response', 'Graph'],
167+
defaultPlugin: 'Graph',
168+
},
163169
copyEndpointOnNewTab: false,
164170
});
165171

166-
// Helper to create mock results from CONSTRUCT query
167-
function createMockResults(query) {
168-
const bindings = [];
169-
170-
// Parse triples from CONSTRUCT clause
171-
const constructMatch = query.match(/CONSTRUCT\s*{([^}]+)}/i);
172-
if (!constructMatch) return { getBindings: () => [] };
173-
174-
const constructBody = constructMatch[1];
175-
const tripleLines = constructBody.split('\n')
176-
.map(line => line.trim())
177-
.filter(line => line && !line.startsWith('PREFIX'));
178-
179-
tripleLines.forEach(line => {
180-
// Simple triple parsing (subject predicate object .)
181-
const match = line.match(/^([^\s]+)\s+([^\s]+)\s+(.+?)\s*\.?\s*$/);
182-
if (match) {
183-
const [, subject, predicate, objectStr] = match;
184-
185-
// Parse object
186-
let object;
187-
if (objectStr.startsWith('"')) {
188-
// Literal
189-
const literalMatch = objectStr.match(/"([^"]+)"(?:\^\^<([^>]+)>)?/);
190-
if (literalMatch) {
191-
object = {
192-
value: literalMatch[1],
193-
type: 'literal',
194-
datatype: literalMatch[2]
195-
};
196-
} else {
197-
object = { value: objectStr.replace(/"/g, ''), type: 'literal' };
198-
}
199-
} else {
200-
// URI or blank node
201-
object = {
202-
value: objectStr.replace(/<|>/g, ''),
203-
type: objectStr.startsWith('_:') ? 'bnode' : 'uri'
204-
};
205-
}
206-
207-
bindings.push({
208-
subject: { value: subject.replace(/<|>/g, ''), type: subject.startsWith('_:') ? 'bnode' : 'uri' },
209-
predicate: { value: predicate.replace(/<|>/g, ''), type: 'uri' },
210-
object: object
211-
});
212-
}
213-
});
214-
215-
return {
216-
getBindings: () => bindings,
217-
getVariables: () => ({ prefixes: {
218-
'ex': 'http://example.org/',
219-
'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
220-
'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
221-
'owl': 'http://www.w3.org/2002/07/owl#',
222-
'xsd': 'http://www.w3.org/2001/XMLSchema#'
223-
}}),
224-
getResponseTime: () => 0,
225-
getType: () => 'construct',
226-
getOriginalResponseAsString: () => '',
227-
getOriginalResponse: () => null,
228-
getError: () => null,
229-
getContentType: () => 'application/sparql-results+json'
230-
};
231-
}
232-
233172
// Add sample query tabs
234173
const tabs = [
235-
{ name: 'Basic Graph', query: queries.basic },
236174
{ name: 'Ontology', query: queries.ontology },
237175
{ name: 'Blank Nodes', query: queries.blankNodes },
238-
{ name: 'Multiple Edges', query: queries.multiplePredicates }
176+
{ name: 'Multiple Edges', query: queries.multiplePredicates },
177+
{ name: 'DESCRIBE Query', query: queries.describe },
178+
{ name: 'Basic Graph', query: queries.basic },
239179
];
240180

241-
// Clear default tab
242-
yasgui.getTab().close();
181+
// Close all existing tabs
182+
let tab;
183+
while ((tab = yasgui.getTab()) != null) {
184+
try {
185+
tab.close();
186+
} catch (e) {
187+
console.error('Failed to close tab:', e);
188+
break;
189+
}
190+
}
243191

244192
// Add sample tabs
245193
tabs.forEach((tab, index) => {
@@ -249,14 +197,7 @@ <h2 onclick="toggleInstructions()">
249197
name: tab.name,
250198
yasqe: { value: tab.query }
251199
}
252-
);
253-
254-
// Simulate query results for demo (since we have no real endpoint)
255-
setTimeout(() => {
256-
// Mock CONSTRUCT results
257-
yasguiTab.yasr.results = createMockResults(tab.query);
258-
yasguiTab.yasr.draw();
259-
}, 100);
200+
);
260201
});
261202
</script>
262203
</body>

demo/queries.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,12 @@ CONSTRUCT {
5656
ex:Person2 ex:knows ex:Person3 .
5757
ex:Person3 ex:knows ex:Person1 .
5858
}
59-
WHERE {}`
59+
WHERE {}`,
60+
61+
describe: `PREFIX ex: <http://example.org/>
62+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
63+
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
64+
65+
# DESCRIBE returns all triples about a resource
66+
DESCRIBE ex:Alice ex:Bob`
6067
};

esbuild.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const config = {
1313
external: [],
1414
// Resolve ES module extensions
1515
loader: {
16-
'.js': 'jsx',
16+
'.js': 'js',
1717
},
1818
};
1919

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@matdata/yasgui-graph-plugin",
33
"private": false,
44
"version": "0.1.0",
5-
"description": "YASGUI plugin for visualizing SPARQL CONSTRUCT query results as interactive graphs",
5+
"description": "YASGUI plugin for visualizing SPARQL CONSTRUCT and DESCRIBE query results as interactive graphs",
66
"main": "dist/yasgui-graph-plugin.min.js",
77
"scripts": {
88
"dev": "vite",

specs/001-construct-graph-viz/checklists/requirements.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,13 @@
3535

3636
**Clarity Assessment**:
3737
- 5 user stories prioritized (P1-P5) with clear dependencies
38-
- 31 functional requirements organized by category (parsing, rendering, interaction, export, edge cases)
38+
- 31 functional requirements organized by category (parsing, rendering, interaction, edge cases)
3939
- 11 edge cases explicitly handled with defined behaviors
4040
- 8 success criteria all measurable and technology-agnostic
4141
- 8 assumptions documented for context
4242

4343
**No Clarifications Needed**: Specification is complete with informed defaults applied:
4444
- Layout algorithm: Force-directed or hierarchical (standard graph viz approach)
45-
- Export format: PNG/SVG (industry standard image formats)
4645
- Performance target: 2 seconds for 1,000 nodes (aligned with constitution)
4746
- Color scheme: Grey/green/blue with semantic meaning (clear visual hierarchy)
4847
- Label truncation: ~50 chars for literals (reasonable reading length)

specs/001-construct-graph-viz/contracts/vis-network-integration.md

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -374,27 +374,6 @@ network.once('stabilizationIterationsDone', () => {
374374
network.setOptions({physics: {enabled: false}});
375375
});
376376
```
377-
378-
## Export Functionality
379-
380-
```javascript
381-
// Get canvas element
382-
const canvas = network.canvas.frame.canvas;
383-
384-
// Export as PNG
385-
canvas.toBlob(blob => {
386-
const url = URL.createObjectURL(blob);
387-
const link = document.createElement('a');
388-
link.href = url;
389-
link.download = 'graph.png';
390-
link.click();
391-
URL.revokeObjectURL(url);
392-
}, 'image/png');
393-
394-
// Or get data URL
395-
const dataURL = canvas.toDataURL('image/png');
396-
```
397-
398377
## Performance Optimization
399378

400379
### Disable Physics After Layout

0 commit comments

Comments
 (0)