I am trying to get the node details (id attribute) when it is right clicked and the contextmenu function is called. I am able to get the node object using var self = d3.select(this); but I am not able to work out the
id attribute of the node (i can see it in the console log though)
I am planning to pass the id to the menu function once I'll get the node.id
JSFiddle
var circle = svg.append("g").selectAll("circle") .data(force.nodes())
.enter().append("circle").attr("r", 6) .call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
var self = d3.select(this);
console.log(self);
var n1=(self[0])[0];
console.log(n1);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
You can pass the datum as a parameter of the function called on the contextmenu event:
.on('contextmenu', function(d) { ... }
which allows you to get the id within the function:
console.log(d.id);
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 500, height = 300;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 400,
height = 200,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(d){
d3.event.preventDefault();
var self = d3.select(this);
var n1=(self[0])[0];
console.log(d.id);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
In addition: For all the ones that want to know how to be able to show node details (for example the node name) in the context menu itself - this is my solution.
The node details can be taken from the "data" element
The placeholder can be taken from the "d" element
The desired information to show in the context menu has to be written into the "text" attribute
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
These lines should be written at the following position:
function createNestedMenu(parent, root, depth = 0) {
var resolve = function (value) {
return utils.toFactory(value).call(root, data, index);
};
parent.selectAll('li')
.data(function (d) {
var baseData = depth === 0 ? menuItems : d.children;
return resolve(baseData);
})
.enter()
.append('li')
.each(function (d) {
var elm = this;
// get value of each data
var isDivider = !!resolve(d.divider);
var isDisabled = !!resolve(d.disabled);
var hasChildren = !!resolve(d.children);
var hasAction = !!d.action;
var text = isDivider ? '<hr>' : resolve(d.title);
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
var listItem = d3.select(this)
.classed('is-divider', isDivider)
.classed('is-disabled', isDisabled)
.classed('is-header', !hasChildren && !hasAction)
.classed('is-parent', hasChildren)
.html(text)
.on('click', function () {
// do nothing if disabled or no action
if (isDisabled || !hasAction) return;
d.action(elm, data, index);
//d.action.call(root, data, index);
closeMenu();
});
if (hasChildren) {
// create children(`next parent`) and call recursive
var children = listItem.append('ul').classed('is-children', true);
createNestedMenu(children, root, ++depth)
}
});
}
Related
I am trying to add a delete node feature in this jsfiddle
The refresh method is adding on("mousedown", mousedownNode) event to each circle, but when I click the node GW2 it deletes the DB node. I figured out that mousedownNode method deletes the correct node but, node = node.data(nodes); in the refresh method is messing up. I am not sure how to fix this problem.
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
EDIT
I have a map which is indexing the node name to index mapping :
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
As you said, the correct nodes are getting deleted and the data bound to the <g class="node"></g> also seems correct BUT you're not UPDATING the node texts.
This is what you have:
nodeEnter
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.id;
});
This is what I've changed it to:
nodeEnter
.append("text")
.attr("dx", 12)
.attr("dy", ".35em");
node.select('text')
.text(function(d) {
return d.id;
});
You can also change this by assigning the nodeText to a variable like this var nodeText = ...
But here's the point, when you call the refresh after say deleting a node, the exit() selection works BUT the existing nodes DO NOT update by themselves. Hope this makes sense. Read How Selections Work
JSFIDDLE and here's the snippet:
EDIT: Removing a draggable SVG element results in an error. To fix, change the exit selection line to:
node.exit().on('mousedown.drag', null).remove();
that removes the drag event bound before removing the element.
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<button id="ref" onclick="refresh()">refresh </button>
<script>
//setInterval(refresh, 15000);
function addNodeCanvas(nodeName,g) {
var node = { x: 900, y: 900, id: nodeName, grp:g };
var n = nodes.push(node);
console.log(node);
refresh();
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {}, t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) { s = curNode; }
if (curNode.id == idTarget) { t = curNode; }
}
});
//console.log( { s,t});
links.push({ source: s, target: t });
};
refresh();
}
var fill = d3.scale.category20();
var links = [{ source: "FH", target: "TP" }];
var nodes = [
{ id: "FH", x: 100, y: 110 },
{ id: "TP", x: 200, y: 110 },
{ id: "GW1", x: 200, y: 110 },
{ id: "GW", x: 200, y: 110 },
{ id: "GW2", x: 200, y: 110 },
{ id: "DB", x: 100, y: 110 }
]
var width = 600,
height = 400,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout
.force()
.size([width, height])
.nodes(nodes) .links(links)
.linkDistance(50)
.charge(-100)
.on("tick", tick);
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
var arrows = svg
.append("svg:defs")
.selectAll("marker")
.data(["arrow"]) // Different link/path types can be defined here
.enter()
.append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", -1)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
svg
.append("rect")
.attr("width", width)
.attr("height", height);
var nodes = force.nodes(),
links = force.links(),
node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function tick() {
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
var angle = Math.atan2(dy, dx);
var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);
dr = Math.sqrt(dx * dx + dy * dy);
return (
"M" +
(d.source.x + offsetX) +
"," +
(d.source.y + offsetY) +
"A" +
dr +
"," +
dr +
" 0 0,1 " +
(d.target.x - offsetX) +
"," +
(d.target.y - offsetY)
);
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
refresh();
function refresh() {
link = link.data(links);
link
.enter()
.append("path")
.attr("class", "link")
.attr("marker-end", "url(#arrow)");
link.exit().remove();
node = node.data(nodes);
var nodeEnter = node
.enter()
.insert("g")
.attr("class", "node")
.on("mousedown", mousedownNode)
.call(force.drag);
nodeEnter
.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
nodeEnter
.append("text")
.attr("dx", 12)
.attr("dy", ".35em");
node.select('text')
.text(function(d) {
return d.id;
});
node.exit().on('mousedown.drag', null).remove();
force.start();
}
</script>
I didn't dig into this much, as I felt like the problem was quite obvious to me. I can't exactly explain why the GW node returns but it's something to do with your data-binding being incorrect I believe.
When you're doing your data join you're basically telling D3 to key on an index. Now the original GW node in the DOM is going to be re-used if it weren't the last in the index because the indexes will be offset by 1.
node = node.data(nodes);
If you change this to key on a field
node = node.data(nodes, d => d.id);
You'll find your problem goes away.
I am trying to add a context menu to the static nodes in this jsfiddle as given in jakosz's example.
On right-clicking the nodes, it does show the menu but at the left top of the screen, I am not able to figure out how to pass the node co-ordinates so the context menu appears around the node rather than somewhere else.
copying the same jsfiddle in the post.
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
menu(d3.mouse(this)[0], d3.mouse(this)[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
You want to get the position of the parent group, not the circle's position:
menu(d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]);
Or, the most idiomatic way, just use the SVG as the container:
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
Here is your code with that change only:
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
Here is a plunkr of a matrix scatterplot in d3 V4
http://plnkr.co/edit/7meh4sMhxItcQtCaAtZP?p=preview
(It may take some time to load and display)
I am looking forward to zoom the plot in order to see the cluttered points in a convenient way.
I have used d3.zoom() to rescale the axis but the circles are not being plotted accordingly.
Here is the problem description:
Initially, all the axis are set with different ranges (like top y-axis is having range from 4.5-7.5 , below that y-axis range is from 2.0-4.0 .. )
Now after zooming it, (i.e when scrolling mouse over the circles), all the axis are set in the same ranges that leads to all circles oriented diagonally.
Is there a workaround for that so that we can zoom the axis accordingly and visualize it nicely !!
Thanks. Any help would highly appreciated.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.cell text {
font-weight: bold;
text-transform: capitalize;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
div.tooltip {
position: absolute;
text-align: center;
width: 100px;
height: 80px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var tooltipDiv;
d3.helper = {};
var zoomable = true;
var new_xScale;
var new_yScale;
d3.helper.tooltip = function (param1, param2) {
var bodyNode = d3.select('body').node();
function tooltip(selection) {
selection.on('mouseover.tooltip', function (point) {
// Clean up lost tooltips
d3.select('body').selectAll('div.tooltip').remove();
// Append tooltip
tooltipDiv = d3.select('body')
.append('div')
.attr('class', 'tooltip');
var absoluteMousePos = d3.mouse(bodyNode);
//console.log('absoluteMousePos', absoluteMousePos);
tooltipDiv
.style('left', (absoluteMousePos[0] + 10) + 'px')
.style('top', (absoluteMousePos[1] - 30) + 'px');
var line = '';
//var temp_key = d3.keys(point);
var temp_key = [param1, param2];
// _.each(d3.keys(point), function (key, index) {
temp_key.forEach(function (key, index) {
if (index != temp_key.length - 1) {
line += key + ': ' + point[key] + '</br>';
} else {
line += key + ': ' + point[key];
}
});
tooltipDiv.html(line);
})
.on('mousemove.tooltip', function () {
// Move tooltip
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv
.style("left", (absoluteMousePos[0] + 10) + 'px')
.style("top", absoluteMousePos[1] < 80 ? absoluteMousePos[1] + 10 :(absoluteMousePos[1] - 70) + 'px');
})
.on('mouseout.tooltip', function () {
// Remove tooltip
tooltipDiv.remove();
});
}
tooltip.attr = function (_x) {
if (!arguments.length) return attrs;
attrs = _x;
return this;
};
tooltip.style = function (_x) {
if (!arguments.length) return styles;
styles = _x;
return this;
};
return tooltip;
};
var width = 500,
size = 150,
padding = 20;
var x = d3.scaleLinear().rangeRound([padding / 2, size - padding / 2]);
var y = d3.scaleLinear().rangeRound([size - padding / 2, padding / 2]);
var xAxis = d3.axisBottom()
.scale(x)
.ticks(7);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(7);
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.json("flowers.json", function (data) {
var attr2Domain = {},
attrs= ['sepal length', 'sepal width', 'petal length','petal width']
n = attrs.length;
attrs.forEach(function (attr) {
attr2Domain[attr] = d3.extent(data, function (ele) {
return ele[attr];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var svg = d3.select("body")
.append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(attrs)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function (d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(attrs)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function (d, i) {
return "translate(0," + i * size + ")";
})
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(attrs, attrs))
.enter()
.append("g")
.attr("class", "cell")
.attr("classx", function(d){ return d.x; })
.attr("classy", function(d){ return d.y; })
.attr("transform", function (d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
// Titles for the diagonal.
cell.filter(function (d) {
return d.i === d.j;
})
.append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.style("fill", "black")
.text(function (d) {
return d.x;
});
cell.each(plot);
// plot each cell
function plot(p) {
cell = d3.select(this);
x.domain(attr2Domain[p.x]);
y.domain(attr2Domain[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.style("pointer-events", "none")
.attr("height", size - padding);
var circles = cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d) {
return x(d[p.x]);
})
.attr("cy", function (d) {
return y(d[p.y]);
})
.attr("r", 4)
//.style("fill", "green");
.style("fill", function (d) {
return color(d.species);
});
circles.on('mousemove', function(){
var param1 = d3.select(this.parentNode).attr("classx");
var param2 = d3.select(this.parentNode).attr("classy");
circles.call(d3.helper.tooltip(param1, param2));
});
}
//--------------------------------------------- applying zoom----------
applyZoom();
function applyZoom() {
if (zoomable) {
var zoom = d3.zoom()
.on("zoom", zoomed);
svg.call(zoom).on("dblclick.zoom", null);
}
}
function zoomed() {
console.log('zoomed');
new_xScale = d3.event.transform.rescaleX(x);
new_yScale = d3.event.transform.rescaleY(y);
console.log("new_xScale", x);
svg.selectAll(".x.axis")
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis.scale(new_xScale));
});
svg.selectAll(".y.axis")
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis.scale(new_yScale));
});
cell.each(plotly);
}
function plotly(p) {
console.log("plotly", p);
//return x(d[p.x])
svg.selectAll("circle")
.attr("cx", function (d) {
return new_xScale(d[p.x]);
})
.attr("cy", function (d) {
return new_yScale(d[p.y]);
});
}
});
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
c.push({x: a[i], i: i, y: b[j], j: j});
}
}
return c;
}
</script>
I think move'new_xScale = d3.event.transform.rescaleX(x)' after 'x.domain(attr2Domain[d])' inside each function should work. The same as y.
Thanks
Here is a parallel coordinate with some sample data
http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview
Here the numerical axis like column1 , column3 have small number of data so it looks ok. But in practice, I have a big data (something like 600 entries), so numerical ticks in axis become clustered , overlapped.
Is there a way so that it looks good. Like Setting the number of ticks in parallel coordinate. For instance, if I want to change the number of ticks in column1 of this example from 10 to 5.
Any help would be appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
var container = d3.select("body").append("div")
.attr("class", "parcoords")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px");
var svg = container.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.json("convertcsv.json", function(error, cars) {
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
console.log(y[d]);
}
else{
vals.sort();
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);
}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
if(y[d].name == 'r'){
// console.log(this);
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel_chart).on("end", brush_end));
}
else if(y[d].name == 'n')
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [15,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel).on("end", brush_end_ordinal));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
}); // closing
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function go_brush() {
d3.event.sourceEvent.stopPropagation();
}
invertExtent = function(y) {
return domain.filter(function(d, i) { return y === range[i]; });
};
function brushstart(selectionName) {
foreground.style("display", "none")
//console.log(selectionName);
var dimensionsIndex = dimensions.indexOf(selectionName);
//console.log(dimensionsIndex);
extents[dimensionsIndex] = [0, 0];
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
//if (d3.event.sourceEvent.type === "brush") return;
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end(){
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
extents[i][0] = Math.round( extents[i][0] * 10 ) / 10;
extents[i][1] = Math.round( extents[i][1] * 10 ) / 10;
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
// brush for ordinal cases
function brush_parallel() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
//var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
//return extents[i][1] <= p_new && p_new <= extents[i][0];
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end_ordinal(){
console.log("hhhhh");
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
if(selected.length >1)
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
</script>
You can either use d3-axis.ticks() or d3-axis.tickValues([]) to specify custom number of ticks/tick values.
For example, for column1 using tickValues and column3 using ticks:
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) {
if(d === 'column1') {
d3.select(this).call(d3.axisLeft(y[d]).tickValues([.1, .2, .3, .5,.8]));
} else if(d === 'column3') {
d3.select(this).call(d3.axisLeft(y[d]).ticks(4));
} else {
d3.select(this).call(d3.axisLeft(y[d]));
}
})
A fork of your plunkr: TickValues and ticks DEMO
Hope this helps. Read the docs for more options.
i have the following simple graph to visualize:
sample graph
First, only 'n1' is shown connecting to its neighbor 'n2'
Second, after 'n2' is clicked, the network expands by adding two edges 'n2'->'n3' and 'n2'->'n4'
Third, after 'n3' is clicked, the network expands by adding two edges 'n3'->'n1' and 'n3'->'n5'.
To do so, i thought i could create an array of nodes that are visible. By clicking a node, this node name will be added into the array. And then in the tick() function the visible nodes in that array will be used to set up the network.
But when I do so with the following code:
// return currently visible nodes as the source nodes
// e.g. called by
// var visible_seeds = seeding(nodes);
//
function seeding(nodes){
var visible = [];
nodes.forEach(function(n){
if(n.name=='n1')
visible.push(n);
});
return visible;
}
The above function is then called as following:
var visible_seeds = seeding(nodes);
var force = d3.layout.force()
.nodes(d3.values(visible_seeds))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
It gives the error
index.html TypeError:'undefined' is not a function (evaluating 'nodes.forEach(function(n){...')
What is the correct way to achieve my goal? Thank you out there!!!
p.s. I am absolutely new to D3 and javascript.
The data read in by D3 are from a csv file:
source,target,value
n1,n2,1.0
n2,n3,1.0
n2,n4,1.0
n3,n1,1.0
n3,n5,1.0
My code is based on the example at:
http://bl.ocks.org/d3noob/5155181
Added - source code - the issue happens in the function seeding()
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<style>
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
</style>
<body>
<script>
// get the data
d3.csv("force.csv", function(error, links) {
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, vis:0});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, vis:0});
link.value = +link.value;
});
//nodes["n1"] = {name:"n1", vis:1};
var vis = ['n1'];
var width = 960,
height = 500;
function seeding(){
var visible = [];
nodes.forEach(function(n){
if(n.name=='n1')
visible.push(n);
});
return visible;
}
var visible_seeds = seeding();
var force = d3.layout.force()
.nodes(d3.values(visible_seeds))
//.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(links, function(d) { return d.value; })]);
// asign a type per value to encode opacity
links.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// action to take on mouse click
function click() {
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 22)
.style("fill", "steelblue")
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16)
.style("fill", "lightsteelblue");
}
// action to take on mouse double click
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6)
.style("fill", "#ccc");
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 12)
.style("stroke", "none")
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
});
</script>
</body>
</html>
First, the source of your error is because nodes is a JavaScript object (not an array), it has no forEach method.
Second, I'm not sure where you are going with your approach. You start the force layout with one node, then what? On click you append the other nodes? How you going to handle the links?
Intead, I'd suggest just toggling the visibility of the nodes and links on click. Here's a quick implementation:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<style>
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
</style>
<body>
<script>
// get the data
//d3.csv("force.csv", function(error, links) {
var links = [{"source":"n1","target":"n2","value":"1.0"},{"source":"n2","target":"n3","value":"1.0"},{"source":"n2","target":"n4","value":"1.0"},{"source":"n3","target":"n1","value":"1.0"},{"source":"n3","target":"n5","value":"1.0 "}];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, vis:0});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, vis:0});
link.value = +link.value;
});
//nodes["n1"] = {name:"n1", vis:1};
var vis = ['n1'];
var width = 300,
height = 300;
for (key in nodes){
var node = nodes[key];
node.visible = (node.name === 'n1');
}
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(links, function(d) { return d.value; })]);
// asign a type per value to encode opacity
links.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)")
.style("opacity", function(d){
return d.target.visible && d.source.visible ? 1 : 0;
})
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.style("opacity", function(d){
return d.visible ? 1 : 0;
})
.on("click", click)
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// action to take on mouse click
function click(d0) {
links.forEach(function(d1){
console.log(d0, d1.source)
if (d1.source === d0) {
d1.target.visible = !d1.target.visible;
}
});
node.style("opacity", function(d){
return d.visible ? 1 : 0;
});
path.style("opacity", function(d){
return d.target.visible && d.source.visible ? 1 : 0;
});
}
//});
</script>
</body>
</html>