diff --git a/packages/core/src/boxes-editor.js b/packages/core/src/boxes-editor.js
index 221208a..a95d81a 100644
--- a/packages/core/src/boxes-editor.js
+++ b/packages/core/src/boxes-editor.js
@@ -224,11 +224,20 @@ export class BoxesEditor {
constructor(container, options = {}) {
if (!container) throw new Error('Container element is required');
this.container = container;
- this.options = { layout: options.layout || { name: 'preset' }, ...options };
+
+ // If a pre-loaded template JSON object is provided, extract its fields.
+ // Explicit options take precedence over template fields when both are supplied.
+ const tmpl = options.template || {};
+ const palette = tmpl.palette || {};
+
+ this.options = { layout: options.layout || tmpl.lastLayout || { name: 'preset' }, ...options };
this._instanceId = Math.random().toString(36).slice(2, 9);
- this.userStylesheet = (options.style || []).map(rule => ({ selector: rule.selector, style: { ...rule.style } }));
- this._nodeTypes = (options.nodeTypes || []).map(t => ({ ...t }));
- this._edgeTypes = (options.edgeTypes || []).map(t => ({ ...t }));
+ this.title = options.title ?? tmpl.title ?? '';
+ this.description = options.description ?? tmpl.description ?? '';
+ const styleSource = options.style ?? tmpl.userStylesheet ?? [];
+ this.userStylesheet = styleSource.map(rule => ({ selector: rule.selector, style: { ...rule.style } }));
+ this._nodeTypes = (options.nodeTypes ?? palette.nodeTypes ?? []).map(t => ({ ...t }));
+ this._edgeTypes = (options.edgeTypes ?? palette.edgeTypes ?? []).map(t => ({ ...t }));
this.currentEdgeType = this._edgeTypes[0] || null;
this.cy = null;
this.eventHandlers = new Map();
@@ -243,7 +252,7 @@ export class BoxesEditor {
this._selectedElement = null;
this._ctxTarget = null;
this._ctxPosition = null;
- this.context = { ...(options.context || {}) };
+ this.context = { ...(options.context ?? tmpl.context ?? {}) };
this._init();
@@ -289,12 +298,29 @@ export class BoxesEditor {
.bxe-pane-title { font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:#888; margin-bottom:8px; }
.bxe-pane-label { font-size:12px; font-weight:600; color:#666; margin-bottom:4px; }
.bxe-pane-label small { font-weight:normal; color:#999; }
-.bxe-palette { display:flex; flex-direction:column; gap:4px; margin-bottom:10px; }
+.bxe-palette { display:flex; flex-direction:column; gap:4px; margin-bottom:4px; }
.bxe-palette-item { display:flex; align-items:center; gap:8px; padding:5px 8px; border:1px solid #dee2e6; border-radius:5px; cursor:pointer; background:#fff; }
.bxe-palette-item:hover { background:#e9f0ff; border-color:#90b8f8; }
.bxe-palette-item.selected { background:#dce8ff; border-color:#4d90fe; }
-.bxe-palette-label { font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
+.bxe-palette-label { font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; }
+.bxe-palette-actions { display:flex; gap:1px; margin-left:auto; opacity:0; flex-shrink:0; }
+.bxe-palette-item:hover .bxe-palette-actions { opacity:1; }
+.bxe-palette-act-btn { background:none; border:none; color:#999; cursor:pointer; font-size:12px; line-height:1; padding:0 3px; border-radius:2px; }
+.bxe-palette-act-btn:hover { color:#0d6efd; background:#e8f0fe; }
+.bxe-palette-act-btn.danger:hover { color:#dc3545; background:#fff0f0; }
.bxe-node-swatch { width:20px; height:20px; border:2px solid #999; flex-shrink:0; }
+.bxe-type-form { border:1px solid #c8d8f8; border-radius:5px; padding:8px; margin-bottom:6px; background:#f0f6ff; font-size:12px; }
+.bxe-type-form-row { display:grid; grid-template-columns:52px 1fr; gap:4px; margin-bottom:4px; align-items:center; }
+.bxe-type-form-row label { font-size:11px; color:#666; font-weight:600; }
+.bxe-type-form-row input, .bxe-type-form-row select, .bxe-type-form-row textarea { width:100%; padding:2px 4px; border:1px solid #ccc; border-radius:3px; font-size:12px; font-family:inherit; box-sizing:border-box; }
+.bxe-type-form-row input[type=color] { padding:1px; height:22px; cursor:pointer; }
+.bxe-type-form-row textarea { resize:vertical; min-height:36px; font-family:monospace; }
+.bxe-type-form-actions { display:flex; gap:4px; margin-top:6px; }
+.bxe-btn-sm { padding:2px 10px; border-radius:3px; font-size:12px; cursor:pointer; }
+.bxe-btn-primary { background:#0d6efd; color:#fff; border:1px solid #0d6efd; }
+.bxe-btn-primary:hover { background:#0a58ca; border-color:#0a58ca; }
+.bxe-btn-secondary { background:#fff; color:#555; border:1px solid #ccc; }
+.bxe-btn-secondary:hover { background:#f0f0f0; }
.bxe-prop-group { margin-bottom:8px; }
.bxe-prop-group > label { display:block; font-size:12px; font-weight:600; color:#666; margin-bottom:2px; }
.bxe-input { width:100%; padding:3px 5px; border:1px solid #ccc; border-radius:3px; font-size:12px; }
@@ -437,10 +463,22 @@ export class BoxesEditor {
this._edgePaletteEl = document.createElement('div');
palettePane.appendChild(this._edgePaletteEl);
this._nodePaletteEl.addEventListener('click', (e) => {
+ const action = e.target.closest('[data-action]')?.dataset.action;
+ if (action === 'edit-node-type') { this._showNodeTypeForm(e.target.closest('[data-action]').dataset.typeId); return; }
+ if (action === 'del-node-type') { this._deleteNodeType(e.target.closest('[data-action]').dataset.typeId); return; }
+ if (action === 'add-node-type') { this._showNodeTypeForm(null); return; }
+ if (action === 'save-node-type') { this._saveNodeTypeForm(); return; }
+ if (action === 'cancel-node-type') { this._nodeTypeFormEl.style.display = 'none'; return; }
const item = e.target.closest('.bxe-palette-item');
if (item) this._selectNodeType(item.dataset.typeId);
});
this._edgePaletteEl.addEventListener('click', (e) => {
+ const action = e.target.closest('[data-action]')?.dataset.action;
+ if (action === 'edit-edge-type') { this._showEdgeTypeForm(e.target.closest('[data-action]').dataset.typeId); return; }
+ if (action === 'del-edge-type') { this._deleteEdgeType(e.target.closest('[data-action]').dataset.typeId); return; }
+ if (action === 'add-edge-type') { this._showEdgeTypeForm(null); return; }
+ if (action === 'save-edge-type') { this._saveEdgeTypeForm(); return; }
+ if (action === 'cancel-edge-type') { this._edgeTypeFormEl.style.display = 'none'; return; }
const item = e.target.closest('.bxe-palette-item');
if (item) this.setEdgeType(item.dataset.typeId);
});
@@ -713,12 +751,13 @@ export class BoxesEditor {
const edgeTypes = this._edgeTypes;
if (!this._nodePaletteEl) return;
+ // ── Node types ──
+ this._nodePaletteEl.innerHTML = '';
+ const nodePalette = document.createElement('div');
+ nodePalette.className = 'bxe-palette';
if (!nodeTypes.length) {
- this._nodePaletteEl.innerHTML = '
No node types defined
';
+ nodePalette.innerHTML = 'No node types defined
';
} else {
- this._nodePaletteEl.innerHTML = '';
- const palette = document.createElement('div');
- palette.className = 'bxe-palette';
nodeTypes.forEach((type, i) => {
const radius = type.shape === 'ellipse' ? '50%' : type.shape === 'roundrectangle' ? '5px' : '2px';
const bg = type.color || '#e0e0e0';
@@ -732,19 +771,33 @@ export class BoxesEditor {
const label = document.createElement('span');
label.className = 'bxe-palette-label';
label.textContent = type.label;
+ const actions = document.createElement('span');
+ actions.className = 'bxe-palette-actions';
+ actions.innerHTML = ``;
item.appendChild(swatch);
item.appendChild(label);
- palette.appendChild(item);
+ item.appendChild(actions);
+ nodePalette.appendChild(item);
});
- this._nodePaletteEl.appendChild(palette);
}
-
+ this._nodePaletteEl.appendChild(nodePalette);
+ this._nodeTypeFormEl = document.createElement('div');
+ this._nodeTypeFormEl.className = 'bxe-type-form';
+ this._nodeTypeFormEl.style.display = 'none';
+ this._nodePaletteEl.appendChild(this._nodeTypeFormEl);
+ const addNodeBtn = document.createElement('button');
+ addNodeBtn.className = 'bxe-btn-add';
+ addNodeBtn.dataset.action = 'add-node-type';
+ addNodeBtn.textContent = '+ Add node type';
+ this._nodePaletteEl.appendChild(addNodeBtn);
+
+ // ── Edge types ──
+ this._edgePaletteEl.innerHTML = '';
+ const edgePalette = document.createElement('div');
+ edgePalette.className = 'bxe-palette';
if (!edgeTypes.length) {
- this._edgePaletteEl.innerHTML = 'No edge types defined
';
+ edgePalette.innerHTML = 'No edge types defined
';
} else {
- this._edgePaletteEl.innerHTML = '';
- const palette = document.createElement('div');
- palette.className = 'bxe-palette';
edgeTypes.forEach((type, i) => {
const color = type.color || '#666666';
const dashArray = type.lineStyle === 'dashed' ? '6,3' : type.lineStyle === 'dotted' ? '2,3' : 'none';
@@ -755,11 +808,24 @@ export class BoxesEditor {
const svgLine = dashArray === 'none'
? ``
: ``;
+ const actions = document.createElement('span');
+ actions.className = 'bxe-palette-actions';
+ actions.innerHTML = ``;
item.innerHTML = `${this._esc(type.label)}`;
- palette.appendChild(item);
+ item.appendChild(actions);
+ edgePalette.appendChild(item);
});
- this._edgePaletteEl.appendChild(palette);
}
+ this._edgePaletteEl.appendChild(edgePalette);
+ this._edgeTypeFormEl = document.createElement('div');
+ this._edgeTypeFormEl.className = 'bxe-type-form';
+ this._edgeTypeFormEl.style.display = 'none';
+ this._edgePaletteEl.appendChild(this._edgeTypeFormEl);
+ const addEdgeBtn = document.createElement('button');
+ addEdgeBtn.className = 'bxe-btn-add';
+ addEdgeBtn.dataset.action = 'add-edge-type';
+ addEdgeBtn.textContent = '+ Add edge type';
+ this._edgePaletteEl.appendChild(addEdgeBtn);
this._currentNodeTypeId = nodeTypes[0]?.id || null;
if (edgeTypes[0]) {
@@ -767,6 +833,138 @@ export class BoxesEditor {
}
}
+ /** Show the inline node type editor form. Pass null typeId to add a new type. */
+ _showNodeTypeForm(typeId) {
+ const type = typeId ? this._nodeTypes.find(t => t.id === typeId) : null;
+ const isNew = !type;
+ const id = type?.id ?? '';
+ const label = type?.label ?? '';
+ const color = type?.color ?? '#cccccc';
+ const borderColor = type?.borderColor ?? '#888888';
+ const shape = type?.shape ?? 'rectangle';
+ const data = type?.data ? JSON.stringify(type.data, null, 2) : '{}';
+ this._nodeTypeFormEl.innerHTML = `
+ ${isNew ? 'Add node type' : 'Edit node type'}
+
+
+
+
+
+
+
+
+
+
+
+
`;
+ this._nodeTypeFormEl.style.display = 'block';
+ this._nodeTypeFormEl.querySelector('[data-field="label"]').focus();
+ }
+
+ _saveNodeTypeForm() {
+ const f = this._nodeTypeFormEl;
+ const id = f.querySelector('[data-field="id"]').value.trim();
+ const label = f.querySelector('[data-field="label"]').value.trim();
+ const color = f.querySelector('[data-field="color"]').value;
+ const borderColor = f.querySelector('[data-field="borderColor"]').value;
+ const shape = f.querySelector('[data-field="shape"]').value;
+ const dataRaw = f.querySelector('[data-field="data"]').value.trim();
+ const origId = f.querySelector('[data-action="save-node-type"]').dataset.origId;
+ if (!id) { alert('ID is required'); return; }
+ if (!label) { alert('Label is required'); return; }
+ let data = {};
+ if (dataRaw && dataRaw !== '{}') {
+ try { data = JSON.parse(dataRaw); } catch { alert('Data must be valid JSON'); return; }
+ }
+ const type = { id, label, color, borderColor, shape, data };
+ if (origId) {
+ const idx = this._nodeTypes.findIndex(t => t.id === origId);
+ if (idx >= 0) this._nodeTypes[idx] = type;
+ } else {
+ if (this._nodeTypes.some(t => t.id === id)) { alert(`A node type with id "${id}" already exists`); return; }
+ this._nodeTypes.push(type);
+ }
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ _deleteNodeType(typeId) {
+ if (!confirm(`Remove node type "${typeId}"?`)) return;
+ this._nodeTypes = this._nodeTypes.filter(t => t.id !== typeId);
+ if (this._currentNodeTypeId === typeId) this._currentNodeTypeId = this._nodeTypes[0]?.id || null;
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Show the inline edge type editor form. Pass null typeId to add a new type. */
+ _showEdgeTypeForm(typeId) {
+ const type = typeId ? this._edgeTypes.find(t => t.id === typeId) : null;
+ const isNew = !type;
+ const id = type?.id ?? '';
+ const label = type?.label ?? '';
+ const color = type?.color ?? '#666666';
+ const lineStyle = type?.lineStyle ?? 'solid';
+ const data = type?.data ? JSON.stringify(type.data, null, 2) : '{}';
+ this._edgeTypeFormEl.innerHTML = `
+ ${isNew ? 'Add edge type' : 'Edit edge type'}
+
+
+
+
+
+
+
+
+
+
+
`;
+ this._edgeTypeFormEl.style.display = 'block';
+ this._edgeTypeFormEl.querySelector('[data-field="label"]').focus();
+ }
+
+ _saveEdgeTypeForm() {
+ const f = this._edgeTypeFormEl;
+ const id = f.querySelector('[data-field="id"]').value.trim();
+ const label = f.querySelector('[data-field="label"]').value.trim();
+ const color = f.querySelector('[data-field="color"]').value;
+ const lineStyle = f.querySelector('[data-field="lineStyle"]').value;
+ const dataRaw = f.querySelector('[data-field="data"]').value.trim();
+ const origId = f.querySelector('[data-action="save-edge-type"]').dataset.origId;
+ if (!id) { alert('ID is required'); return; }
+ if (!label) { alert('Label is required'); return; }
+ let data = {};
+ if (dataRaw && dataRaw !== '{}') {
+ try { data = JSON.parse(dataRaw); } catch { alert('Data must be valid JSON'); return; }
+ }
+ const type = { id, label, color, lineStyle, data };
+ if (origId) {
+ const idx = this._edgeTypes.findIndex(t => t.id === origId);
+ if (idx >= 0) this._edgeTypes[idx] = type;
+ } else {
+ if (this._edgeTypes.some(t => t.id === id)) { alert(`An edge type with id "${id}" already exists`); return; }
+ this._edgeTypes.push(type);
+ }
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ _deleteEdgeType(typeId) {
+ if (!confirm(`Remove edge type "${typeId}"?`)) return;
+ this._edgeTypes = this._edgeTypes.filter(t => t.id !== typeId);
+ if (this.currentEdgeType?.id === typeId) this.currentEdgeType = this._edgeTypes[0] || null;
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+
_selectNodeType(typeId) {
this._currentNodeTypeId = typeId;
if (this._nodePaletteEl) {
@@ -1540,6 +1738,12 @@ export class BoxesEditor {
return cls.includes('eh-ghost') || cls.includes('eh-preview');
};
return {
+ title: this.title,
+ description: this.description,
+ palette: {
+ nodeTypes: this._nodeTypes.map(t => ({ ...t })),
+ edgeTypes: this._edgeTypes.map(t => ({ ...t })),
+ },
elements: {
nodes: (els.nodes || []).filter(el => !isEhGhost(el)).map(cleanEl),
edges: (els.edges || []).filter(el => !isEhGhost(el)).map(cleanEl)
@@ -1558,6 +1762,23 @@ export class BoxesEditor {
* Import graph data.
*/
importGraph(graphData) {
+ if (graphData.title !== undefined) {
+ this.title = graphData.title;
+ }
+ if (graphData.description !== undefined) {
+ this.description = graphData.description;
+ }
+ if (graphData.palette) {
+ if (graphData.palette.nodeTypes) {
+ this._nodeTypes = graphData.palette.nodeTypes.map(t => ({ ...t }));
+ this._currentNodeTypeId = this._nodeTypes[0]?.id || null;
+ }
+ if (graphData.palette.edgeTypes) {
+ this._edgeTypes = graphData.palette.edgeTypes.map(t => ({ ...t }));
+ this.currentEdgeType = this._edgeTypes[0] || null;
+ }
+ this._renderPalette();
+ }
if (graphData.elements) {
this.loadElements(graphData.elements);
}
@@ -1969,6 +2190,60 @@ export class BoxesEditor {
return this._edgeTypes.map(t => ({ ...t }));
}
+ /** Add a new node type to the palette. Re-renders the palette. */
+ addNodeType(type) {
+ if (!type.id) throw new Error('Node type requires an id');
+ if (this._nodeTypes.some(t => t.id === type.id)) throw new Error(`Node type "${type.id}" already exists`);
+ this._nodeTypes.push({ ...type });
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Update an existing node type by id. Re-renders the palette. */
+ updateNodeType(id, updates) {
+ const idx = this._nodeTypes.findIndex(t => t.id === id);
+ if (idx < 0) throw new Error(`Node type "${id}" not found`);
+ this._nodeTypes[idx] = { ...this._nodeTypes[idx], ...updates };
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Remove a node type by id. Re-renders the palette. */
+ removeNodeType(id) {
+ this._nodeTypes = this._nodeTypes.filter(t => t.id !== id);
+ if (this._currentNodeTypeId === id) this._currentNodeTypeId = this._nodeTypes[0]?.id || null;
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Add a new edge type to the palette. Re-renders the palette. */
+ addEdgeType(type) {
+ if (!type.id) throw new Error('Edge type requires an id');
+ if (this._edgeTypes.some(t => t.id === type.id)) throw new Error(`Edge type "${type.id}" already exists`);
+ this._edgeTypes.push({ ...type });
+ if (!this.currentEdgeType) this.currentEdgeType = this._edgeTypes[0];
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Update an existing edge type by id. Re-renders the palette. */
+ updateEdgeType(id, updates) {
+ const idx = this._edgeTypes.findIndex(t => t.id === id);
+ if (idx < 0) throw new Error(`Edge type "${id}" not found`);
+ this._edgeTypes[idx] = { ...this._edgeTypes[idx], ...updates };
+ if (this.currentEdgeType?.id === id) this.currentEdgeType = { ...this._edgeTypes[idx] };
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
+ /** Remove an edge type by id. Re-renders the palette. */
+ removeEdgeType(id) {
+ this._edgeTypes = this._edgeTypes.filter(t => t.id !== id);
+ if (this.currentEdgeType?.id === id) this.currentEdgeType = this._edgeTypes[0] || null;
+ this._renderPalette();
+ this._emit('paletteChanged', { nodeTypes: this.getNodeTypes(), edgeTypes: this.getEdgeTypes() });
+ }
+
/**
* Add a node of a named type at an optional position.
*/
diff --git a/packages/core/src/index.js b/packages/core/src/index.js
index 236f87b..9a10307 100644
--- a/packages/core/src/index.js
+++ b/packages/core/src/index.js
@@ -1,5 +1,5 @@
export { BoxesEditor } from './boxes-editor.js';
-export { defaultTemplates } from './templates.js';
+export { defaultTemplates, getTemplate, listTemplates, loadTemplateFromUrl } from './templates.js';
export { exportToTurtle, importFromTurtle, rdfExporter, rdfImporter } from './io/rdf.js';
export {
exportToJsonLD, importFromJsonLD, jsonldExporter, jsonldImporter,
diff --git a/packages/core/src/templates.js b/packages/core/src/templates.js
index e60ebf4..28a6826 100644
--- a/packages/core/src/templates.js
+++ b/packages/core/src/templates.js
@@ -1,288 +1,16 @@
/**
- * Template configurations for common graph types
+ * Template configurations for common graph types.
+ * Templates are stored as JSON files and re-exported here.
*/
-export const defaultTemplates = {
- 'owl-ontology': {
- name: 'Ontology',
- description: 'Template with OWL and SKOS meta-types styling (CMap Ontology edition)',
- context: {
- 'owl': 'http://www.w3.org/2002/07/owl#',
- 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
- 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
- 'skos': 'http://www.w3.org/2004/02/skos/core#',
- 'sh': 'http://www.w3.org/ns/shacl#',
- // Term definitions: properties whose values are IRI references, not literals.
- // These enable JSON-LD @type coercion so that plain string values like
- // "owl:Thing" are expanded to full IRIs rather than treated as string literals.
- 'rdfs:subClassOf': { '@type': '@id' },
- 'rdfs:domain': { '@type': '@id' },
- 'rdfs:range': { '@type': '@id' },
- 'rdfs:subPropertyOf': { '@type': '@id' },
- 'rdfs:seeAlso': { '@type': '@id' },
- 'rdfs:isDefinedBy': { '@type': '@id' },
- 'owl:equivalentClass': { '@type': '@id' },
- 'owl:equivalentProperty': { '@type': '@id' },
- 'owl:inverseOf': { '@type': '@id' },
- 'owl:onProperty': { '@type': '@id' },
- 'owl:allValuesFrom': { '@type': '@id' },
- 'owl:someValuesFrom': { '@type': '@id' },
- 'owl:imports': { '@type': '@id' },
- 'skos:broader': { '@type': '@id' },
- 'skos:narrower': { '@type': '@id' },
- 'skos:related': { '@type': '@id' },
- 'skos:broadMatch': { '@type': '@id' },
- 'skos:narrowMatch': { '@type': '@id' },
- 'skos:exactMatch': { '@type': '@id' },
- 'skos:closeMatch': { '@type': '@id' },
- 'sh:path': { '@type': '@id' },
- 'sh:node': { '@type': '@id' },
- 'sh:qualifiedValueShape': { '@type': '@id' },
- 'sh:property': { '@type': '@id' },
- 'sh:class': { '@type': '@id' },
- 'sh:datatype': { '@type': '@id' },
- },
- nodeTypes: [
- { id: 'owl:Class', label: 'Class', data: { '@type': 'owl:Class','@id':'', 'skos:definition':'' }, color: '#E6F3FF', borderColor: '#2471A3', shape: 'roundrectangle' },
- { id: 'default', label: 'Instance', data: {'@type':'', '@id':''}, color: '#FFFFFF', borderColor: '#666666', shape: 'ellipse' },
- { id: 'skos:Concept', label: 'Concept', data: { '@type': 'skos:Concept','@id':'', 'skos:definition':'' }, color: '#E6F3FF', borderColor: '#2471A3', shape: 'roundrectangle' },
- { id: 'owl:Ontology', label: 'Ontology', data: { '@type': 'owl:Ontology', '@id':''}, color: '#FFFACD', borderColor: '#B8860B', shape: 'roundrectangle' },
- ],
- edgeTypes: [
- { id: 'rdfs:subClassOf', label: 'are', data: { '@id': 'rdfs:subClassOf' }, color: '#555555', lineStyle: 'solid' },
- { id: 'rdf:type', label: 'a', data: { '@id': 'rdf:type' }, color: '#8E44AD', lineStyle: 'solid' },
- { id: 'skos:related', label: 'related', data: {'@id':'skos:related'}, color: '#666666', lineStyle: 'solid' },
- { id: 'skos:broader', label: 'broader', data: {'@id':'skos:broader'}, color: '#777777', lineStyle: 'solid' },
- { id: 'skos:narrower', label: 'narrower', data: {'@id':'skos:narrower'}, color: '#777777', lineStyle: 'solid' },
- {
- id: 'owl:ObjectProperty',
- label: 'ObjectProperty',
- data: { '@type': 'owl:ObjectProperty' },
- color: '#2471A3',
- lineStyle: 'dashed',
- // Reified property mapping
- source_property: 'rdfs:domain',
- target_property: 'rdfs:range',
- },
- {
- id: 'owl:DatatypeProperty',
- label: 'DatatypeProperty',
- data: { '@type': 'owl:DatatypeProperty' },
- color: '#1E8449',
- lineStyle: 'solid',
- // Reified property mapping
- source_property: 'rdfs:domain',
- target_property: 'rdfs:range',
- },
- {
- id: 'QualifiedPropertyShape',
- label: 'Qualified Property Shape',
- data: {
- '@type': 'sh:PropertyShape',
- 'sh:path': '', // This would be the property being qualified
- 'sh:qualifiedMinCount': 0, // Default cardinality
- },
- color: '#2471A3',
- lineStyle: 'dashed',
- // Reified property mapping for SHACL Qualified property shapes
- target_property: 'sh:qualifiedValueShape',
- reverse_source_property: 'sh:property' // This indicates that the source node is connected via sh:property to the QualifiedPropertyShape
- },
- {
- id: 'PropertyShape',
- label: 'Property Shape',
- data: {
- '@type': 'sh:PropertyShape',
- 'sh:path': '', // This would be the property being qualified
- },
- color: '#2471A3',
- lineStyle: 'dashed',
- // Reified property mapping for SHACL Qualified property shapes
- target_property: 'sh:node',
- reverse_source_property: 'sh:property' // This indicates that the source node is connected via sh:property to the QualifiedPropertyShape
- },
- ],
- elements: {
- nodes: [],
- edges: []
- },
- style: [
- // Default node - plain concept box
- {
- selector: 'node',
- style: {
- 'shape': 'ellipse',
- 'background-color': '#FFFFFF',
- 'border-width': 2,
- 'border-color': '#666666',
- 'width': 'label',
- 'height': 'label',
- 'padding': '8px',
- 'text-valign': 'center',
- 'text-halign': 'center',
- 'font-size': '13px',
- 'color': '#000000',
- 'min-width': '60px'
- }
- },
- // owl:Ontology - yellow box (CMap Ontology style)
- {
- selector: 'node[\\@type = "owl:Ontology"]',
- style: {
- 'shape': 'roundrectangle',
- 'background-color': '#FFFACD',
- 'border-width': 1,
- 'border-color': '#B8860B',
- 'font-weight': 'bold',
- 'color': '#000000'
- }
- },
- // owl:Class - blue rounded box
- {
- selector: 'node[\\@type = "owl:Class"]',
- style: {
- 'shape': 'roundrectangle',
- 'background-color': '#E6F3FF',
- 'border-width': 1,
- 'border-color': '#2471A3',
- 'font-size': '14px',
- 'font-weight': 'bold',
- 'color': '#154360'
- }
- },
- // rdfs:Class
- {
- selector: 'node[\\@type = "rdfs:Class"]',
- style: {
- 'shape': 'roundrectangle',
- 'background-color': '#E6F3FF',
- 'border-width': 1,
- 'border-color': '#2471A3',
- 'font-size': '14px',
- 'font-weight': 'bold',
- 'color': '#154360'
- }
- },
- // Default edge
- {
- selector: 'edge',
- style: {
- 'label': 'data(label)',
- 'line-color': '#666666',
- 'target-arrow-color': '#666666',
- 'target-arrow-shape': 'triangle',
- 'curve-style': 'bezier',
- 'font-size': '11px',
- 'text-background-color': '#FFFFFF',
- 'text-background-opacity': 0.8,
- 'text-background-padding': '2px',
- 'width': 1.5
- }
- },
- // owl:ObjectProperty edge
- {
- selector: 'edge[\\@type = "owl:ObjectProperty"]',
- style: {
- 'line-color': '#2471A3',
- 'target-arrow-color': '#2471A3',
- 'width': 2
- }
- },
- // owl:DatatypeProperty edge - dashed
- {
- selector: 'edge[\\@type = "owl:DatatypeProperty"]',
- style: {
- 'line-color': '#1E8449',
- 'target-arrow-color': '#1E8449',
- 'line-style': 'dashed',
- 'width': 1.5
- }
- },
- // rdfs:subClassOf - hollow triangle arrow (inheritance)
- {
- selector: 'edge[\\@id = "rdfs:subClassOf"]',
- style: {
- 'line-color': '#555555',
- 'target-arrow-color': '#555555',
- 'target-arrow-shape': 'triangle',
- 'width': 2
- }
- },
- // rdf:type edge
- {
- selector: 'edge[\\@id = "rdf:type"]',
- style: {
- 'line-color': '#884EA0',
- 'target-arrow-color': '#884EA0',
- 'line-style': 'dotted',
- 'width': 1.5
- }
- }
- ]
- },
-
- 'arrows': {
- name: 'Arrows',
- description: 'Basic graph template similar to Arrows (Neo4j)',
- nodeTypes: [
- { id: 'default', label: 'Node', data: {}, color: '#6FB1FC', borderColor: '#3A7CC5', shape: 'ellipse' }
- ],
- edgeTypes: [
- { id: 'default', label: 'RELATES_TO', data: {}, color: '#6FB1FC', lineStyle: 'solid' }
- ],
- elements: {
- nodes: [],
- edges: []
- },
- style: [
- {
- selector: 'node',
- style: {
- 'background-color': '#6FB1FC',
- 'shape': 'ellipse',
- 'width': '80px',
- 'height': '80px',
- 'border-width': 3,
- 'border-color': '#3A7CC5',
- 'color': '#000000',
- 'font-size': '13px',
- 'text-valign': 'center',
- 'text-halign': 'center'
- }
- },
- {
- selector: 'edge',
- style: {
- 'label': 'data(label)',
- 'line-color': '#9DB8D2',
- 'target-arrow-color': '#9DB8D2',
- 'target-arrow-shape': 'triangle',
- 'width': 2,
- 'curve-style': 'bezier',
- 'font-size': '11px',
- 'text-background-color': '#FFFFFF',
- 'text-background-opacity': 0.8,
- 'text-background-padding': '2px'
- }
- }
- ]
- },
+import blankTemplate from './templates/blank.json';
+import arrowsTemplate from './templates/arrows.json';
+import owlOntologyTemplate from './templates/owl-ontology.json';
- 'blank': {
- name: 'Blank',
- description: 'Empty graph with default styling',
- nodeTypes: [
- { id: 'default', label: 'Node', data: {}, color: '#CCCCCC', borderColor: '#888888', shape: 'rectangle' }
- ],
- edgeTypes: [
- { id: 'default', label: 'edge', data: {}, color: '#666666', lineStyle: 'solid' }
- ],
- elements: {
- nodes: [],
- edges: []
- },
- style: []
- }
+export const defaultTemplates = {
+ 'blank': blankTemplate,
+ 'arrows': arrowsTemplate,
+ 'owl-ontology': owlOntologyTemplate,
};
/**
@@ -292,13 +20,24 @@ export function getTemplate(name) {
return defaultTemplates[name] || defaultTemplates['blank'];
}
+/**
+ * Fetch a template JSON file from a URL and return the parsed object.
+ * @param {string} url
+ * @returns {Promise