Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added help and cypher export #1

Open
wants to merge 2 commits into
base: gh-pages
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 134 additions & 8 deletions cypher.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
function cypher(model) {
function Cypher() {
this.format = function(model) {
function props(element) {
var props = {};
element.properties().list().forEach(function (property) {
Expand All @@ -12,6 +13,7 @@ function cypher(model) {
}

function quote(name) {
if (name == undefined || name == null || name.trim() == "") return null;
return isIdentifier(name) ? name : "`" + name + "`";
}

Expand All @@ -30,17 +32,141 @@ function cypher(model) {

var statements = [];
model.nodeList().forEach(function (node) {
statements.push("(" + quote(node.id) +" :" + quote(node.caption() || "Node") + " " + render(props(node)) + ") ");
var labels = node.caption().split(/[:\s]+/).map(quote).filter(function(l) { return l !== undefined }).map(function(l) { return ":" + l;}).join("");
statements.push("(" + quote(""+node.id) + labels + " " + render(props(node)) + ") ");
});
model.relationshipList().forEach(function (rel) {
statements.push("(" + quote(rel.start.id) +
")-[:`" + quote(rel.relationshipType()||"RELATED_TO") +
// " " + TODO render(props(rel)) +
"`]->("+ quote(rel.end.id) +")"
")-[:" + quote(rel.relationshipType()||"RELATED_TO") +
" " + render(props(rel)) +
"]->("+ quote(rel.end.id) +")"
);
});
if (statements.length==0) return "";
return "CREATE \n " + statements.join(",\n ");
};
if (typeof exports != "undefined") exports.cypher=cypher
gd.cypher=function(model) {return cypher(model || this.model());}
}
this.parse = function(cypher, opts) {
if (typeof(cypher) != "string") {
console.log("Cannot parse",cypher)
return {nodes:[],links:[]}
}
var time = Date.now();
var keep_names = opts && opts.keep_names;
var nodes = {}
var rels = []
var PARENS = /(\s*\),?\s*|\s*\(\s*)/;
function toArray(map) {
var res = [];
for (var k in map) {
if (map.hasOwnProperty(k)) {
res.push(map[k]);
}
}
return res;
}
function splitClean(str, pattern, clean) {
if (clean) { str = str.replace(clean,""); }
var r = str.split(pattern)
.map(function(s) { return s.trim(); })
.filter(function(s) { return s.trim().length > 0 && !s.match(pattern); });
return r;
}
function keyIndex(key,map) {
var count=0;
for (k in map) {
if (key == k) return count;
count+=1;
}
return -1;
}
cypher = cypher.replace(/CREATE/ig,"");
var parts = splitClean(cypher,PARENS);
var id=0;
var lastNode, lastRel;
var NODE_PATTERN=/^\s*(`[^`]+`|\w+)\s*((?::\w+|:`[^`]+`)*)\s*(\{.+\})?\s*$/;
var REL_PATTERN=/^(<)?\s*-\s*(?:\[(`[^`]+`|\w+)?\s*(:(?:`[^`]+`|[\w]+))?\s*(\{.+\})?\])?\s*-\s*(>)?$/;
var PROP_PATTERN=/^\s*`?(\w+)`?\s*:\s*(".+?"|'.+?'|\[.+?\]|.+?)\s*(,\s*|$)/;
var ARRAY_VALUES_PATTERN=/^\s*(".+?"|'.+?'|.+?)\s*(,\s*|$)/;
parts.forEach(function(p,i) {
function parseProps(node,props) {
function escapeQuotes(value) {
value = value.trim().replace(/(^|\W)'([^']*?)'(\W|$)/g,'$1"$2"$3');
if (value[0]=='"') value = '"'+value.substring(1,value.length-1).replace(/"/g,'\\"') + '"';
return value;
}
function parseArray(value) {
value = value.substring(1,value.length-1); // eliminate []
var res="";
while (_val = value.match(ARRAY_VALUES_PATTERN)) {
value = value.substring(_val[0].length); // next part
var element = escapeQuotes(_val[1]);
if (res!="") res += ",";
res += element;
}
return "[" + res + "]";
}
function isArray(value) { return value[0] == "["; }
var prop = null;
props = props.substring(1,props.length-1); // eliminate {}
while (prop = props.match(PROP_PATTERN)) {
props = props.substring(prop[0].length); // next part
var pname = prop[1];
var value = prop[2];
value = isArray(value) ? parseArray(value) : escapeQuotes(value);
node[pname]=JSON.parse(value);
}
return node;
}
function parseInner(m) {
var name=m[1] ? m[1].replace(/`/g,"") : m[1];
var labels=[];

var props=""; // TODO ugly
if (m.length > 1) {
if (m[2] && m[2][0]==":") labels = splitClean(m[2],/:/,/`/g); /*//*/
else props=m[2] || "";
if (m.length>2 && m[3] && m[3][0]=="{") props=m[3];
}

return parseProps( {_id:id,_name:name,_labels:labels}, props);
}
var m = null;
if (m = p.match(NODE_PATTERN)) {
var node = parseInner(m);
var name=node["_name"];
if (!keep_names) delete(node["_name"]);
if (!nodes[name]) {
nodes[name]=node;
id += 1;
}
lastNode=name;
if (lastRel) {
if (lastRel.source===null) lastRel.source=keyIndex(name,nodes);
if (lastRel.target===null) lastRel.target=keyIndex(name,nodes);
}
} else {
if (m = p.match(REL_PATTERN)){
var incoming = m[1]=="<" && m[5]!=">";
m.splice(5,1); m.splice(1,1);
var rel=parseInner(m);
rel["_type"]=rel["_labels"][0];
if (!keep_names) delete(rel["_name"]);
delete(rel["_id"]);delete(rel["_labels"]);
rel["source"]= incoming ? null : keyIndex(lastNode,nodes);
rel["target"]= incoming ? keyIndex(lastNode,nodes) : null;
lastRel=rel;
rels.push(rel);
}
}
})
if (opts && opts.measure) console.log("time",Date.now()-time);
return {nodes: toArray(nodes), links: rels};
}
}

if (typeof exports != "undefined") {
exports.cypher=Cypher
}

gd.formatCypher=function(model) {return new Cypher().format(model || this.model());}
gd.parseCypher=function(model) {return new Cypher().parse(model || this.model());}
65 changes: 59 additions & 6 deletions graph-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ window.onload = function()
}

d3.selectAll( ".btn.cancel" ).on( "click", cancelModal );
d3.selectAll( ".modal" ).on( "keyup", function() { if ( d3.event.keyCode === 27 ) cancelModal(); } );
d3.selectAll( ".modal" ).on( "keyup",
function() { if ( d3.event.keyCode === 27 ) cancelModal(); }
);

function appendModalBackdrop()
{
Expand All @@ -338,7 +340,7 @@ window.onload = function()

var markup = formatMarkup();
d3.select( "textarea.code" )
.attr( "rows", markup.split( "\n" ).length * 2 )
.attr( "rows", Math.max(10,markup.split( "\n" ).length * 2) )
.node().value = markup;
};

Expand All @@ -360,8 +362,6 @@ window.onload = function()
cancelModal();
};

d3.select( "#save_markup" ).on( "click", useMarkupFromMarkupEditor );

var exportSvg = function ()
{
var rawSvg = new XMLSerializer().serializeToString(d3.select("#canvas svg" ).node());
Expand All @@ -380,16 +380,66 @@ window.onload = function()
return true;
};

var useCypherFromEditor = function ()
{
var cypher = d3.select( ".export-cypher .modal-body textarea.code" ).node().value;
d3Model = gd.parseCypher( cypher );
graphModel = modelFromD3( d3Model );
save(formatMarkup());
draw();
cancelModal();
};

var modelFromD3 = function( data ) {
function convert(value) {
if (typeof(value) == "string" && value.length > 20) return value.substring(0,20)+" ...";
return value;
}
var width = 500;
var height = 500;
var progress = 0;
var selection = d3.select("#tmp ul.graph-diagram-markup");
var model = gd.markup.parse(selection); // only to copy style attributes
data.nodes.forEach(function(nodeData) {
var id = parseInt(nodeData["_id"]);
var node = model.createNode(id);
node.class("node");
var angle = 0.6 * Math.PI * progress;
node.x(nodeData["x"] || Math.cos(angle) * width * 0.3 * Math.round(1 + progress / 3) + width);
node.y(nodeData["y"] || Math.sin(angle) * height * 0.3 * Math.round(1 + progress / 3) + height);
progress += 1;
node.caption(nodeData["_labels"].join(" "))
Object.keys(nodeData).forEach(function(prop) {
if (!["_id","class","x","y","_labels"].includes(prop)) {
var value = nodeData[prop];
if (typeof(value) == "string" && value.length > 20) value = value.substring(0,20)+" ...";
node.properties().set(prop,convert(value));
}
})
})
data.links.forEach(function(relData) {
var rel = model.createRelationship(model.lookupNode(relData["source"]),model.lookupNode(relData["target"]));
rel.class("relationship");
rel.relationshipType(relData["_type"])
Object.keys(relData).forEach(function(prop) {
if (!["_id","class","x","y","_type","source","target"].includes(prop)) {
rel.properties().set(prop,convert(relData[prop]));
}
})
})
return model;
}

d3.select( "#open_console" ).on( "click", openConsoleWithCypher );

var exportCypher = function ()
{
appendModalBackdrop();
d3.select( ".modal.export-cypher" ).classed( "hide", false );

var statement = gd.cypher(graphModel);
var statement = gd.formatCypher(graphModel);
d3.select( ".export-cypher .modal-body textarea.code" )
.attr( "rows", statement.split( "\n" ).length )
.attr( "rows", Math.max(10,statement.split( "\n" ).length) )
.node().value = statement;
};

Expand Down Expand Up @@ -429,5 +479,8 @@ window.onload = function()
d3.event.stopPropagation();
} );

d3.select( "#save_markup" ).on( "click", useMarkupFromMarkupEditor );
d3.select( "#save_cypher" ).on( "click", useCypherFromEditor );

draw();
};
13 changes: 8 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName( 'script' )[0];
s.parentNode.insertBefore( ga, s );
})();
}) // ();

</script>
</head>
Expand All @@ -49,7 +49,7 @@
<h3>Edit/Export markup</h3>
</div>
<div class="modal-body">
<textarea class="code">Some text</textarea>
<textarea class="code" rows="10" cols="80">Some text</textarea>
</div>
<div class="modal-footer">
<a href="#" class="btn cancel">Cancel</a>
Expand Down Expand Up @@ -137,16 +137,19 @@ <h3>Edit Relationship</h3>
<h3>Export as Cypher</h3>
</div>
<div class="modal-body">
<textarea class="code">Some Text</textarea>
<textarea class="code" rows="10" cols="80">Some Text</textarea>
</div>
<div class="modal-footer">
<a href="#" class="btn cancel btn-primary">Close</a>
<a href="#" class="btn cancel">Cancel</a>
<a href="#" class="btn btn-primary" id="save_cypher">Save</a>
<a href="#" target="_blank" class="btn" id="open_console">Open in Console</a>
</div>
</div>

<div id="canvas"></div>


<div id="tmp">
<ul class="graph-diagram-markup" data-internal-scale="1" data-external-scale="1"></ul>
</div>
</body>
</html>