Getting the node details in the context menu in D3 - javascript

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

D3 v4 Parallel Coordinate Plot Brush Selection

The parallel coordinate plot we are using and the data for the plot can be found here. This parallel coordinate plot does not work with version 4 of d3. We have made changes based on the API changes from v3 to v4. I think the main issue is in the brush function shown below.
function brush() {
let actives = dimensions.filter(function (p) {
return d3.brushSelection(y[p]) !== null;
});
console.log(actives);
let extents = actives.map(function (p) {
return d3.brushSelection(y[p]);
});
foreground.style("display", function (d) {
return actives.every(function (p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
The log shows "Array []" for actives. Currently we set each dimensions brush extent to be [[-8,0],[8,height]], which may be an issue as well. The full code is provided below.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
}
.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="https://d3js.org/d3.v4.min.js"></script>
<script>
let margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let x = d3.scalePoint().range([0, width]).padding(1),
y = {},
dragging = {};
let line = d3.line(),
axis = d3.axisLeft(), //Argument for axisLeft? Compare to code on original plot
background,
foreground;
let svg = d3.select("body").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 + ")");
d3.csv("cars.csv", function (error, cars) {
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(cars[0]).filter(function (d) {
return d !== "name" && (y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function (p) {
return +p[d];
}))
.range([height, 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.
let 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.
g.append("g")
.attr("class", "axis")
.each(function (d) {
d3.select(this).call(axis.scale(y[d]));
})
.append("text")
.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) {
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8,0],[8,height]]).on("start", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
});
function position(d) {
let 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 brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
//return !y[p].brush.empty was the original return value.
let actives = dimensions.filter(function (p) {
return d3.brushSelection(y[p]) !== null;
});
console.log(actives);
let extents = actives.map(function (p) {
return d3.brushSelection(y[p]);
});
foreground.style("display", function (d) {
return actives.every(function (p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
</script>
If anyone is familiar with d3 and could offer any guidance it would be greatly appreciated. We also tried using d3.event.selection and y[p].brush.selection in the brush function.
I stumbled upon the exact same issue but managed to resolve it after below changes.
Add brush for each axis this way:
y[d] = d3.scaleLinear().domain(d3.extent(data, function(p) {
return +p[d];
})).range([height, 0]);
y[d].brush = d3.brushY()
.extent([[-8, y[d].range()[1]], [8, y[d].range()[0]]])
.on('brush', brush);
Subsequently, give above as the brush callback when adding the brush group:
g.append('g')
.attr('class', 'brush')
.each(function(d) {
d3.select(this).call(y[d].brush);
})
.selectAll('rect')
.attr('x', -8)
.attr('width', 16);
Finally, change the brush handler to be:
function brush() {
const actives = [];
// filter brushed extents
svg.selectAll('.brush')
.filter(function(d): any {
return d3.brushSelection(this as any);
})
.each(function(d) {
actives.push({
dimension: d,
extent: d3.brushSelection(this as any)
});
});
// set un-brushed foreground line disappear
foreground.style('display', function(d) {
return actives.every(function(active) {
const dim = active.dimension;
return active.extent[0] <= y[dim](d[dim]) && y[dim](d[dim]) <= active.extent[1];
}) ? null : 'none';
});
}
If above is confusing, see this standalone example that helped me with correctly brushing on parallel coordinates with d3 v4 : https://gist.github.com/kotomiDu/d1fd0fe9397db41f5f8ce1bfb92ad20d

Fitting a d3.js Map to a container

I currently have a d3.js map of the world using the world-50m.json however I am having problems fitting the entire map into a container.
Here is a picture of what the problem is:
My Code:
d3.json("libraries/world-50m.json", function(error, world) {
var mapRatio = .5;
var width = 1600;
var height = 800;
var svg = d3.select("div#chart svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 " + Math.min(width, height) + " " + Math.min(width, height));
var projection = d3.geo.equirectangular()
.scale(height / Math.PI)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
if (error) {
return console.error(error);
}
svg.append("g")
.attr("height", height)
.attr("width", width)
.attr("class", "land")
.selectAll("path")
.data([topojson.object(world, world.objects.land)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("height", height)
.attr("width", width)
.attr("class", "boundary")
.selectAll("boundary")
.data([topojson.object(world, world.objects.countries)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("class", "graticule")
.selectAll("path")
.data(graticule.lines)
.enter().append("path")
.attr("d", path);
var svg = d3.select("#chart svg").append("g");
var serverVerticesURL = JSON_URL;
var coordinateData = $.getJSON(
serverVerticesURL,
function (data, status) {
if (status === "success") {
// No error detected,
var vertexJSON = data;
var coordinates = [];
// Check that we have data to map.
if (vertexJSON.length > 0) {
// Plot points on map...
vertexJSON.forEach(function(column)
{
var value = column.location;
var origin = column.vertex;
var col1 = "Longitude";
var col2 = "Latitude";
var atkLong = value.match(/(.*)~.*/);
var atkLat = value.match(/.*~(.*)/);
coordinates.push([atkLong[1], atkLat[1], origin]);
});
if (typeof data.message === "string") {
vertexJSON = [];
}
var atkNodeSelection = svg.selectAll("circle");
var honeypotNode = svg.selectAll("circle");
// Appending attack nodes
var atkNode = atkNodeSelection.data(coordinates)
.enter().append("svg:circle")
.attr("class", "atkNode")
.attr("r", 5)
.attr("cx", function(d) { return projection([parseFloat(d[0]), parseFloat(d[1])])[0]; })
.attr("cy", function(d) { return projection([parseFloat(d[0]), parseFloat(d[1])])[1]; });
// Appending honeypot node
var hpnode = honeypotNode.data([[-73.934543,41.722841]])
.enter().append("svg:circle")
.attr("class", "honeypot")
.attr("r", 7)
.attr("cx", function(d) { return projection([d[0], d[1]])[0]; })
.attr("cy", function(d) { return projection([d[0], d[1]])[1]; });
var link = svg.selectAll(".link").data(coordinates).enter().append("line").attr("class", "link")
.attr("x1", function(d) { return projection([parseFloat(d[0]), parseFloat(d[1])])[0]; })
.attr("y1", function(d) { return projection([parseFloat(d[0]), parseFloat(d[1])])[1]; })
.attr("x2", function(d) { return projection([-73.934543,41.722841])[0]; })
.attr("y2", function(d) { return projection([-73.934543,41.722841])[1]; })
.attr("d", function (d) {
var x1 = projection([parseFloat(d[0]), parseFloat(d[1])])[0],
y1 = projection([parseFloat(d[0]), parseFloat(d[1])])[1],
x2 = projection([-73.934543,41.722841])[0],
y2 = projection([-73.934543,41.722841])[1],
// Defaults for normal edge.
drx = 0,
dry = 0,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2;
});
Structure of the container:
<div id="#chart">
<svg>
<g class="land | boundary | graticule">
</g>
</svg>
</div>
css:
/*==================================================
* Map
* ===============================================*/
.graticule {
fill: #3385ff;
fill-opacity: .3;
stroke: #777;
stroke-width: 0.5px;
stroke-opacity: 0.5;
}
.land {
fill: #d0d0e1;
margin-left: 0px;
}
.boundary {
fill: none;
stroke: #0066ff;
stroke-width: 0.5px;
}
/*==================================================
* Attack Nodes
* ===============================================*/
.atkNode {
fill: red;
shape-rendering: auto;
fill-opacity: .7;
stroke-opacity: .8;
z-index: 2002;
}
.atkNode:hover {
fill: #0039e6;
fill-opacity: .5;
}

Wrapping long text labels in D3 without extra new lines [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Utilizing Mike Bostock’s Wrapping Long Labels function I was able to wrap long text labels in D3.js. However I see that my D3 chart inserts extra new lines when a particular text needs to be wrapped into more than two lines. Can you please help me to wrap without these extra new lines?
Here are my label data and Mike Bostock’s code I used.
treeData = {
'name': 'Good Short Label',
'parent': 'null',
'_children': [
{'name': 'Very Very Long Good Wapped Label'},
{'name': 'Very Very Very Very Very Very Very Very Very Long Label With Extra New Line'},
{'name': 'Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Long Label With Extra New Lines'}
]
};
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr('x'),
y = text.attr('y'),
dy = 0, //parseFloat(text.attr('dy')),
tspan = text.text(null)
.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', dy + 'em');
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = text.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', ++lineNumber * lineHeight + dy + 'em')
.text(word);
}
}
});
}
Here is the jsfiddle link to replicate this.
Just don't increment the lineHeight variable
.attr('dy', ++lineNumber * lineHeight + dy + 'em') // add a newline multiple times
.attr('dy', lineHeight + dy + 'em') // adds only one new line
var treeData = {
'name': 'Good Short Label',
'parent': 'null',
'_children': [
{'name': 'Very Very Long Good Wapped Label'},
{'name': 'Very Very Very Very Very Very Very Very Very Long Label With Extra New Line'},
{'name': 'Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Long Label With Extra New Lines'}
]
};
var margin = {top: 20, right: 120, bottom: 20, left: 200};
var width = 950 - margin.right - margin.left;
var height = 800 - margin.top - margin.bottom;
var i = 0;
var duration = 750;
var root;
var tree = d3.layout.tree().size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.y, d.x];
});
var svg = d3.select('#tree').append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
root = treeData;
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style('height', '800px');
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function (d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll('g.node')
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', function (d) {
return 'translate(' + source.y0 + ',' + source.x0 + ')';
})
.on('click', click);
nodeEnter.append('circle')
.attr('r', 1e-6)
.style('fill', function (d) {
return d._children ? '#ccff99' : '#fff';
});
nodeEnter.append('text')
.attr('x', function (d) {
return d.children || d._children ? -13 : 13;
})
.attr('dy', '.35em')
.attr('text-anchor', function (d) {
return d.children || d._children ? 'end' : 'start';
})
.text(function (d) {
return d.name;
})
.call(wrap, 150)
.style('fill-opacitsy', 1e-6)
.attr('class', function (d) {
if (d.url != null) {
return 'hyper';
}
})
.on('click', function (d) {
$('.hyper').attr('style', 'font-weight:normal');
d3.select(this).attr('style', 'font-weight:bold');
})
;
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr('transform', function (d) {
return 'translate(' + d.y + ',' + d.x + ')';
});
nodeUpdate.select('circle')
.attr('r', 10)
.style('fill', function (d) {
return d._children ? '#ccff99' : '#fff';
});
nodeUpdate.select('text')
.style('fill-opacity', 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr('transform', function (d) {
return 'translate(' + source.y + ',' + source.x + ')';
})
.remove();
nodeExit.select('circle')
.attr('r', 1e-6);
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// Update the links…
var link = svg.selectAll('path.link')
.data(links, function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert('path', 'g')
.attr('class', 'link')
.attr('d', function (d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr('d', diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr('d', function (d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr('x'),
y = text.attr('y'),
dy = 0, //parseFloat(text.attr('dy')),
tspan = text.text(null)
.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', dy + 'em');
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = text.append('tspan')
.attr('x', x)
.attr('y', y)
.attr('dy', lineHeight + dy + 'em')
.text(word);
}
}
});
}
#vid-container {
width: 100%;
height: 100%;
width: 820px;
height: 461.25px;
float: none;
clear: both;
margin: 2px auto;
}
svg {
border-radius: 3px;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: #99ccff;;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #99ccff;
stroke-width: 2px;
}
.hyper {
color: red;
text-decoration: underline;
}
.hyper:hover {
color: yellow;
text-decoration: none;
}
.selected {
font-weight: bold;
}
.not-selected {
font-weight: normtal;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='tree'></div>

I need to draw a chord in d3 in eclipse shape rather as circle

Here is my sample js file i need to draw a chord but in eclipse shape rather then a circle. Secondly i need to know that what is a matrix use while creating chord diagram and are we able to draw a chord with simple json file (without using matrix) as mentioned http://www.delimited.io/blog/2013/12/8/chord-diagrams-in-d3 here. Because in every example of chord some matrix is give to draw it. I am new to d3 i need to learn lot of things. Can any one help really appreciate it
var outerRadius = 500 / 2,
innerRadius = outerRadius - 100;
var fill = d3.scale.category20c();
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(innerRadius + 20);
var svg = d3.select('#content').append("svg")
.attr("width", outerRadius * 2)
.attr("height", outerRadius * 2)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius +
")");
d3.json("readme.json", function(error, imports) {
if (error) throw error;
var indexByName = d3.map(),
nameByIndex = d3.map(),
matrix = [],
n = 0;
// Returns the Flare package name for the given class name.
function name(name) {
return name.substring(0, name.lastIndexOf(".")).substring(6);
}
// Compute a unique index for each package name.
imports.forEach(function(d) {
if (!indexByName.has(d = name(d.name))) {
nameByIndex.set(n, d);
indexByName.set(d, n++);
}
});
// Construct a square matrix counting package imports.
imports.forEach(function(d) {
var source = indexByName.get(name(d.name)),
row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
d.imports.forEach(function(d) { row[indexByName.get(name(d))]++; });
});
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", fade(.02))
.on("mouseout", fade(.80));
// .on("mouseover", mouseover);
//.on("mouseout", fade(1));
g.append("svg:path")
.style("stroke", "none")
.style("fill", function(d) { return fill(d.index); })
.attr("d", arc);
/* g.append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", arc);*/
g.append("text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (innerRadius + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return nameByIndex.get(d.index); });
svg.selectAll(".chord")
.data(chord.chords)
.enter().append("path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(d.source.index)).darker(); })
.style("fill", function(d) { return fill(d.source.index); })
//.style("opacity", 1)
.attr("d", d3.svg.chord().radius(innerRadius));
});
d3.select(self.frameElement).style("height", outerRadius * 5 + "px");
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#tooltip {
color: white;
opacity: .9;
background: #333;
padding: 5px;
border: 1px solid lightgrey;
border-radius: 5px;
position: absolute;
z-index: 10;
visibility: hidden;
white-space: nowrap;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
path.group {
fill-opacity: .8;
}
path.chord {
fill-opacity: .8;
stroke: #000;
stroke-width: .25px;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<div id="tooltip"></div>
<script src="lib/d3.js"></script>
<script src="lib/underscore.js"></script>
<script src="js/mapper.js"></script>
<script>
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
d3.csv('data/CNV.csv', function (error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('chr_no')
.addValuesToMap('MUT')
mpr .setFilter(function (row, a, b) {
return (row.chr_no === a.name && row.MUT === b.name) ||
(row.chr_no === b.name && row.MUT === a.name)
})
.setAccessor(function (recs, a, b) {
if (!recs[0]) return 0;
return recs[0].MUT === a.name ? +recs[0].chr_start :
+recs[0].chr_stop ;
});
drawChords(mpr.getMatrix(), mpr.getMap());
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords (matrix, mmap) {
var w = 980, h = 800, r1 = h / 2, r0 = r1 - 110;
var fill = d3.scale.ordinal()
.range(['#c7b570','#c6cdc7','#335c64','#768935',
'#507282','#5c4a56','#aa7455','#574109','#837722',
'#73342d','#0a5564','#9c8f57','#7895a4','#4a5456',
'#b0a690','#0a3542',]);
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r0 + 20);
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
svg.append("circle")
.attr("r", r0 + 20);
var rdr = chordRdr(matrix, mmap);
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
g.append("svg:path")
.style("stroke", "black")
.style("fill", function(d) { return fill(rdr(d).gname); })
.attr("d", arc);
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2;
})
.attr("dy", ".35em")
.style("font-family", "helvetica, arial, sans-serif")
.style("font-size", "9px")
.attr("text-anchor", function(d) { return d.angle > Math.PI ?
"end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return rdr(d).gname; });
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(rdr(d).sname)).darker(); })
.style("fill", function(d) { return fill(rdr(d).sname); })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(chordTip(rdr(d)))
.style("top", function () { return (d3.event.pageY -
170)+"px"})
.style("left", function () { return (d3.event.pageX -
100)+"px";})
})
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
function chordTip (d) {
var p = d3.format(".0%"), q = d3.format("0d")
return "Choromosome information:<br/>"
+ q(d.sname) + " overlap with " + d.tname +" " +
"<br/>chromosome starts at"+" "+ d.sdata +" " +
"<br/>chromosome ends at"+ " "+ d.tdata
}
}
function mouseover(d, i) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(groupTip(rdr(d)))
.style("top", function () { return (d3.event.pageY - 80)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
chordPaths.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
}
</script>

unable to display a tooltip on the bundle layout

I'm trying to reproduce this (bundle layout): http://bl.ocks.org/mbostock/7607999 and it's fine.
However, I'd like to add a tooltip on each link when I hover a node (the tooltip would explain why items are linked together). I have absolutely no clue how to do this. I've tried all sorts of codes, but as I don't really understand it, it's tough. Any idea?
I'd like the tooltip to be shown either on each link (say, in middle) (but I'm afraid that the tooltips related to the highlighted links will not be much visible/legible), or either to aggregate the text of each tooltip highlighted links in a div put somewhere below the graph.
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #bbb;
}
.node:hover {
fill: #000;
}
.link {
stroke: steelblue;
stroke-opacity: .4;
fill: none;
pointer-events: none;
}
.node:hover,
.node--source,
.node--target {
font-weight: 700;
}
.node--source {
fill: #2ca02c;
}
.node--target {
fill: #d62728;
}
.link--source,
.link--target {
stroke-opacity: 1;
stroke-width: 2px;
}
.link--source {
stroke: #d62728;
}
.link--target {
stroke: #2ca02c;
}
/* test_tooltip addition
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
*/
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div>
<script>
// test_tooltip addition
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.attr("x", 200)
.attr("y", 200)
.attr("width", 400)
.attr("height", 100)
.attr("fill", "aliceblue")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0)
//.style("visibility", "hidden")
.text("a simple tooltip");
// end of addition
var diameter = 480, // diameter = 960 ou 480
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.layout.cluster()
.size([360, innerRadius])
.sort(null)
.value(function(d) { return d.size; });
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) { return -d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var link = svg.append("g").selectAll(".link"),
node = svg.append("g").selectAll(".node");
d3.json("test.json", function(error, classes) {
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageLinks(nodes); // links = packageImports(nodes);
link = link
.data(bundle(links))
.enter().append("path")
.each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
.attr("class", "link")
.attr("d", line);
// .attr("d", line); //original
/*
// test_tooltip addition
.attr("d", line)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(d.RG)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// test_tooltip addition
*/
node = node
.data(nodes.filter(function(n) { return !n.children; }))
// skip if URL not desired
.enter().append('a')
.attr("xlink:href", function(d){return d.url;})
.append("text")
// skip until there
// .enter().append("text")
.attr("class", "node")
.attr("dy", ".31em")
// put the parents on the left rather than on the right
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + ((-d.y) - 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
//.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
.style("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
.text(function(d) { return d.key /*+ " truc"; */})
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);
});
function mouseovered(d) {
node
.each(function(n) { n.target = n.source = false; });
link
.classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
.classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
.filter(function(l) { return l.target === d || l.source === d; })
.each(function() { this.parentNode.appendChild(this); });
node
.classed("node--target", function(n) { return n.target; })
.classed("node--source", function(n) { return n.source; });
}
function mouseouted(d) {
link
.classed("link--target", false)
.classed("link--source", false);
node
.classed("node--target", false)
.classed("node--source", false);
}
d3.select(self.frameElement).style("height", diameter + "px");
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports/links for the given array of nodes.
function packageLinks(nodes) { //function packageImports(nodes) {
var map = {},
links = []; // imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import/link, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.links) d.links.forEach(function(i) { //if (d.imports) d.imports.forEach(function(i) {
links.push({source: map[d.name], target: map[i]}); //imports.push({source: map[d.name], target: map[i]});
});
});
return links; //return imports;
}
/*
// test_tooltip addition
d3.select("body")
.append("svg:circle")
.attr("stroke", "black")
.attr("fill", "aliceblue")
.attr("r", 50)
.attr("cx", 52)
.attr("cy", 52)
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
// test_tooltip addition
*/
</script>
</div>
Your code for tool-tip will comes on second place ... first you have to check that at-least your link handle the mouse event or not
defiantly not why ?
because of this part in css
.link {
stroke: steelblue;
stroke-opacity: .4;
fill: none;
pointer-events: none; <====
}
remove pointer events line and than check your tool-tip code of mouseover/mouseout event it will work ...
FYI:
there is reason why in bundle layout has pointer-events set to none ...
because there will be one circle in middle that handle drag and it will allow to rotate this diagram ... if you remove "pointer-events:none " than your events on links start listen and it will mess with drag event of that middle circle ..
so be careful with this "pointer-events:none" if you don't have that rotational functionality than it's OK but you need both at a same time.. you need to manage that with some other option like provide button that will set "pointer-events:none" for while when you rotate and once rotation done again remove that pointer-events:node or apply pointer-events:all so you link tool-tip will work ..
Hope this helps

Categories

Resources