
webapp.js.d3-graph-visualizer-nomodule.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of corese-server Show documentation
Show all versions of corese-server Show documentation
Corese is a Semantic Web Factory (triple store and SPARQL endpoint) implementing RDF, RDFS, SPARQL 1.1
Query and Update.
The newest version!
class GraphModel {
constructor() {
this.nodeRadius = 10;
}
}
GraphModel.BNODE_ID = "bnode";
GraphModel.URI_ID = "uri";
GraphModel.LITERAL_ID = "literal";
class ConfGraphModal {
/**
*
* @param id id given to the DOM node containing the window.
* @param root node parent of the configuration window.
* @param graph Reference to the object responsible of the graph management.
*/
constructor(id, root, graph, data) {
this.id = id;
this.nodeGroups = this.computeGroups(data.nodes);
this.edgeGroups = this.computeGroups(data.edges);
this.domNode = root.append("div")
.attr("id", this.id)
.attr("class", "modal modal-sm")
.html(
this.createLabelsLi(this.nodeGroups, this.edgeGroups)
);
d3.select("body")
.on("keydown", function (that) {
return function () {
const key = d3.event.key;
let numGroup = -1.0;
if (isFinite(key)) {
numGroup = parseInt(key) - 1;
// Changing for a more natural mapping: 1 -> first element,
// 2 -> second, etc. 0 -> 10th element.
if (numGroup === -1) {
numGroup = 10;
}
if (d3.event.ctrlKey) {
if (numGroup < that.edgeGroups.size) {
const groupToSwitch = Array.from(that.edgeGroups)[numGroup];
that.getGroupCheckbox("edges", groupToSwitch).property("checked", !that.getGroupCheckbox("edges", groupToSwitch).property("checked"));
}
} else {
if (numGroup < that.nodeGroups.size) {
const groupToSwitch = Array.from(that.nodeGroups)[numGroup];
that.getGroupCheckbox("nodes", groupToSwitch).property("checked", !that.getGroupCheckbox("nodes", groupToSwitch).property("checked"));
}
}
graph.updateConfiguration();
graph.ticked();
} else if (key === "n") {
if (d3.event.ctrlKey) {
that.edgesCheckbox.check();
} else {
that.nodesCheckbox.check();
}
graph.updateConfiguration();
graph.ticked();
} else {
console.log("key" + key);
}
}
}(this)
);
this.nodesCheckbox = d3.select("#nodesCheckbox");
this.edgesCheckbox = d3.select("#edgesCheckbox");
this.closeButton = d3.select("#configurationGraphCloseButton");
this.setHierarchicalCheckboxesHandler("nodes", this.nodesCheckbox, this.nodeGroups, graph);
this.setHierarchicalCheckboxesHandler("edges", this.edgesCheckbox, this.edgeGroups, graph);
this.closeButton
.on("click", e => {
this.displayOff();
});
}
setHierarchicalCheckboxesHandler( groupName, fatherCheckbox, groups, graph ) {
// 1. compute sub-checkboxes.
var groupCheckboxes = new Set();
groups.forEach(
group => groupCheckboxes.add( this.getGroupCheckbox(groupName, group))
);
// when father checkbox is changed all sub-checkboxes are changed to the same value.
fatherCheckbox.on("change",
function (container) {
return function (evt) {
groupCheckboxes.forEach(
checkbox => checkbox.property("checked", container.property("checked"))
)
graph.updateConfiguration();
graph.ticked();
}
}(fatherCheckbox)
);
fatherCheckbox.check = function() {
fatherCheckbox.property("checked", !fatherCheckbox.property("checked"));
groupCheckboxes.forEach(
checkbox => checkbox.property("checked", fatherCheckbox.property("checked"))
)
graph.updateConfiguration();
graph.ticked();
}
// when one of subcheckboxes is checked, update if necessary the father checkbox :
// - if all subcheckboxes are unset, unset the father checkbox ;
// - if all subcheckboxes are set, set the father checkbox.
groupCheckboxes.forEach(button =>
function (father) {
return button.on("change", function () {
var allChecked = true;
groupCheckboxes.forEach(
button => allChecked = allChecked && button.property("checked")
);
father.property("checked", allChecked);
graph.updateConfiguration();
graph.ticked();
})
}(fatherCheckbox)
);
}
getGroups(groupName) {
if (groupName === "nodes") {
return this.nodeGroups;
} else if (groupName === "edges") {
return this.edgeGroups;
} else {
throw `incorrect groupName value = ${groupName}`;
}
}
computeGroups(data) {
var result = new Set();
data.forEach(
elem => {
if (elem.group === undefined) {
elem.group = "default";
}
if (!result.has(elem.group)) {
result.add(elem.group);
}
}
)
console.log(`found groups: ${result}`)
return result;
}
createLabelsLi(nodeGroups, edgeGroups) {
var result =
"
`;
numGroup++;
} );
}
return result;
}
getGroupCheckbox(groupName, group) {
return d3.select( '#'+this.getCheckboxName(groupName, group) );
}
static createConfigurationPanel(rootConfPanel, graph, data) {
var result = d3.select("#configurationGraph");
if (result.size() === 0) {
var confGraphModal = new ConfGraphModal("configurationGraph", rootConfPanel, graph, data);
return confGraphModal;
} else {
return result;
}
}
isDisplayOn() {
return d3.select(`#${this.id}`)
.style("display") === "block";
}
displayOn() {
d3.select(`#${this.id}`)
.style("display", "block")
.style("top", d3.event.y + "px")
.style("left", d3.event.x + "px");
}
displayOff() {
d3.select(`#${this.id}`)
.style("display", "none");
}
}
class D3GraphVisualizer {
constructor() {
this.model = new GraphModel();
// this.simulation = undefined;
var sheet = document.createElement('style');
document.head.appendChild(sheet); // Bug : does not support multiple graphs in the same page.
}
dragstarted(_simulation) {
return function(d) {
if (!d3.event.active) _simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
}
dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
dragended( _simulation ) {
return function(d) {
if (!d3.event.active) _simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
// To be used with text for edges, in order to obtain text no upside-down.
buildPathFromEdge(scale) {
return links => {
return (edge, i, edges) => {
var dx = edge.source.x - edge.target.x;
var dy = edge.source.y - edge.target.y;
var r = 10 * Math.sqrt(dx*dx + dy*dy);
var dr = r / (2 * edge.linknum);
// var dr = 100/edge.linknum * scale; //linknum is defined above
var lefterpoint, righterpoint;
var sourceLeft = edge.source.x <= edge.target.x;
[lefterpoint, righterpoint] = (sourceLeft) ? [edge.source, edge.target] : [edge.target, edge.source];
var leftx = lefterpoint.x * scale;
var lefty = lefterpoint.y * scale;
var rightx = righterpoint.x * scale;
var righty = righterpoint.y * scale;
var sweep = (sourceLeft) ? "1" : "0";
return `M ${leftx},${lefty} A ${dr * scale},${dr * scale} 0 0,${sweep} ${rightx},${righty}`
};
}
}
/**
* \param svgId : id of the svg element to draw the graph (do not forget the #).
*/
static drawRdf(_results, svgId) {
var results = _results;
var visualizer = new D3GraphVisualizer();
var confGraphModal;
var graph = d3.select(svgId);
results.edges.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
for (var i=0; i {
var current = d3.select(nodes[i]);
var father = d3.select(nodes[i].parentNode);
var image = father.select("image");
if (image !== undefined) {
var width = current.attr("r") * Math.sqrt(2);
image.attr("x", d => (d.x * scale - width / 2));
image.attr("y", d => (d.y * scale - width / 2));
}
}
);
if (graph.displayNodeLabels()) {
textNodes
.attr("x", d => d.x * scale)
.attr("y", d => d.y * scale);
}
if (graph.displayEdgeLabels()) {
pathLabels.attr("d", visualizer.buildPathFromEdge(scale)(results.links));
}
};
graph.zoomed = function () {
var copieTransform = new d3.event.transform.constructor(d3.event.transform.k, d3.event.transform.x, d3.event.transform.y);
copieTransform.k = 1;
g.attr("transform", copieTransform);
graph.ticked(d3.event.transform.k);
};
var scale = 1;
var fo = graph.append('foreignObject').attr("width", "40px").attr("height", "34px");
var button = fo.append("xhtml:button")
.attr("class", "btn btn-info")
.attr("id", "configurationButton")
.on("click", e => {
if (confGraphModal.isDisplayOn()) {
confGraphModal.displayOff()
} else {
confGraphModal.displayOn();
}
});
button.append("xhtml:span")
.attr("class", "glyphicon glyphicon-cog");
results.links = results.edges;
var rootConfPanel = d3.select(d3.select(svgId).node().parentNode, graph);
confGraphModal = ConfGraphModal.createConfigurationPanel(rootConfPanel, graph, results);
graph.updateConfiguration = function () {
return function () {
var updateSet = function(groupName, text) {
var visibleNodes = new Set();
confGraphModal.getGroups(groupName).forEach(
group => {
var checkbox = confGraphModal.getGroupCheckbox(groupName, group);
(checkbox.property("checked")) ? visibleNodes.add(group) : undefined;
}
);
const nodesDisplayCriteria = (d, i, nodes) => (visibleNodes.has(d.group)) ? "visible" : "hidden";
text.attr(
"visibility",
(d, i, nodes) => nodesDisplayCriteria(d, i, nodes)
);
}
updateSet("nodes", textNodes);
updateSet("edges", textEdges);
// textEdges.attr("visibility", confGraphModal.edgesCheckbox.property("checked") ? "visible" : "hidden");
};
}(confGraphModal);
var maxLen = [];
results.nodes.forEach(
(node, index, array) => {
maxLen[node.id] = 0;
}
);
results.links.forEach(
(link, index, array) => {
maxLen[link.source] = Math.max(maxLen[link.source], link.label.length);
maxLen[link.target] = Math.max(maxLen[link.target], link.label.length);
}
);
var color = d3.scaleOrdinal( d3.schemeCategory20 );
visualizer.simulation = d3.forceSimulation(results.nodes)
.force("link", d3.forceLink().id(function (d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
// .force("center", d3.forceCenter(width, height))
.force("center", d3.forceCenter(800, 500))
.on("tick", graph.ticked);
var width = +graph.node().getBoundingClientRect().width;
var height = +graph.node().getBoundingClientRect().height;
visualizer.simulation
.force("link")
.links(results.links);
visualizer.simulation
.force("center", d3.forceCenter(width / 2, height / 2));
var g = graph.append("g")
.attr("class", "everything");
var defs = graph.append("defs");
defs.append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -50 100 100')
.attr('refX', 130)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('xoverflow', 'visible')
.attr('markerUnits', 'userSpaceOnUse')
.append('svg:path')
.attr('d', 'M 0,-20 L 100 ,0 L 0,20')
// .style('stroke','grey')
.style('markerUnits', 'userSpaceOnUse')
.style('fill', 'grey')
;
var links = g.append("g")
.attr("class", "links")
.selectAll("path")
.data(results.links)
.enter().append("path")
.attr(
"class",
d => {
if (d.class !== undefined) {
return d.class;
} else {
return "default";
}
}
)
.attr("id", d => `${d.id}_edge` );
links.append("title")
.text(function (d) {
return d.label;
});
var textNodes;
var nodes = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(results.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.append("circle")
.attr(
"class",
d => {
if (d.class !== undefined) {
return d.class;
} else {
return "default";
}
}
)
.attr("r", visualizer.model.nodeRadius)
.each(
(d, i, nodes) => {
var current = d3.select(nodes[i]);
var father = d3.select(current.node().parentNode);
var color = d3.scaleOrdinal(d3.schemeCategory20).domain(Array.from(confGraphModal.getGroups("nodes")));
d.colorMap = color;
if (d.bg_image === undefined) {
current.attr("fill", color(d.group));
current.attr("r", 5);
} else {
current.attr("r", visualizer.model.nodeRadius);
var width = Math.sqrt(2) * current.attr("r");
father.append("image")
.attr("xlink:href", d.bg_image)
.attr("height", width)
.attr("width", width)
.attr("pointer-events", "none");
}
}
)
.each(
(_links => {
return (d, i, nodes) => {
var neighbors = new Set();
_links.forEach( link => {
if (link.source.id === d.id || link.target.id == d.id) {
neighbors.add( link.source.id );
neighbors.add( link.target.id );
}
});
d.neighbors = neighbors;
}
})(results.links)
)
.call(d3.drag()
.on("start", visualizer.dragstarted(visualizer.simulation))
.on("drag", visualizer.dragged)
.on("end", visualizer.dragended(visualizer.simulation)))
.on("click", (d) => {
if (d.url !== undefined) window.open(d.url);
if (d.link !== undefined) trans(d.link);
})
.on("mouseover", (d, i , nodes) => {
var center = d3.select(nodes[i]);
var datum = center.datum();
var counter = 0;
nodes.forEach( node => {
if (datum.neighbors.has(d3.select(node).datum().id)) {
d3.select(node).attr("fill", "red");
d3.select(node).attr("background-color", "yellow");
d3.select(textNodes.nodes()[counter]).attr("visibility", "visible");
}
counter++;
})
})
.on("mouseout", (d, i, nodes) => {
var center = d3.select(nodes[i]);
var datum = center.datum();
var counter = 0;
nodes.forEach( node => {
var datumNode = d3.select(node).datum();
if (datum.neighbors.has(datumNode.id)) {
d3.select(node).attr("fill", datumNode.colorMap(datumNode.group));
d3.select(node).attr("background-color", "white");
d3.select(textNodes.nodes()[counter]).attr("visibility", "hidden");
}
counter++;
})
});
nodes.append("title")
.text(function (d) {
return d.label;
});
textNodes = g.append("g").attr("class", "texts").selectAll("text")
.data(visualizer.simulation.nodes())
.enter().append("text")
.attr("class", (edge, i, edges) => {
return (edge.class !== undefined) ? edge.class : "default";
})
.text(function (d) {
return d.label;
});
var textEdges = g.append("g").attr("class", "textPaths").selectAll("text")
.data(results.links)
.enter().append("text")
.append("textPath")
.attr("xlink:href", d => {return `#${d.id}`;})
.attr("startOffset", "25%")
.text(function (d) {
return d.label;
})
.attr("class", (edge, i, edges) => {
return (edge.class !== undefined) ? edge.class : "default";
})
.attr("xlink:href", (edge, i, edges) => {
return "#" + edge.id;
});
var displayEdgeLabels = false;
var displayNodeLabels = false;
graph.updateConfiguration();
var pathLabels = graph.append("defs").attr("class", "paths").selectAll("path")
.data(results.links)
.enter().append("path")
.attr("id", (edge, i, edges) => {
return edge.id;
})
.attr("d", visualizer.buildPathFromEdge(1)(results.links));
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", graph.zoomed);
zoom_handler(graph);
}
}
";
return result;
}
getCheckboxName( groupName, group) {
return `${groupName}-${group}Checkbox`;
}
addGroups( groupName, groups ) {
var result = "";
if (groups !== undefined) {
var numGroup = 1;
groups.forEach( group => {
result += `
© 2015 - 2025 Weber Informatics LLC | Privacy Policy