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
Related
I basically copied the example https://bl.ocks.org/skokenes/a85800be6d89c76c1ca98493ae777572
Then I got the code to work with my data. So, I can now get the lasso to work.
But when I try to add back my old code for the circles to display a text-type tool tip, the lasso breaks. The code then puts the class variables such as not_possible or selected on the "text" elements rather than on the "circle" elements where they need to be.
I found that is the issue by using Chrome developer tools.
When the tool tips code is commented out, the lasso code works and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="7" class="selected"></circle>
When the tool tips code is live, the tool tips work but the lasso code doesn't work and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="4.9">
<title r="3.5" class> ==$0
"curr = 89.7, prev = 89.5, geo = Alaska, measure = Percent Citizen, Born in the US"
</title>
</circle>
I've tried changing the styles for the classes, for example, from ".possible" to "circle.possible" but that doesn't help. I've googled for suggestions but haven't found anything that I could make work. I've tried passing the circle selection thru lasso.items(circles) but that doesn't work.
This is the lasso code that does work: the troublesome ".append title" and "text" lines are commented out.
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 960 - margin.left - margin.right
, height = 960 - margin.top - margin.bottom;
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([height, 0]);
var svgArea = d3.select('.content')
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart');
var main = svgArea.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(d3.axisBottom(xScale));
main.append('g')
.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 10)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Current X");
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(d3.axisLeft(yScale));
main.append('g')
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", 0 - margin.left / 2)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Previous Y");
var rScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ 4, 5 ]);
var lasso_start = function() {
lasso.items()
.attr("r",7)
.classed("not_possible",true)
.classed("selected",false)
;
};
var lasso_draw = function() {
lasso.possibleItems()
.classed("not_possible",false)
.classed("possible",true)
;
lasso.notPossibleItems()
.classed("not_possible",true)
.classed("possible",false)
;
};
var lasso_end = function() {
lasso.items()
.classed("not_possible",false)
.classed("possible",false)
;
lasso.selectedItems()
.classed("selected",true)
.attr("r", 7)
;
lasso.notSelectedItems()
.attr("r", 3.5)
;
};
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
//.append("title")
//.text(function(d) {
// return "curr = " + d[1] +
// ", prev = " + d[0] +
// ", geo = " + d[2] +
// ", measure = " + d[3];
// })
;
var lasso = d3.lasso()
.items(circles)
.closePathDistance(75) // max distance for the lasso loop to be closed
.closePathSelect(true) // can items be selected by closing the path?
.targetArea(svgArea) // area where the lasso can be started
.on("start",lasso_start) // lasso start function
.on("draw",lasso_draw) // lasso draw function
.on("end",lasso_end); // lasso end function
svgArea.call(lasso);
Why does including ".title" and ".text" cause a problem?
And how do I solve it?
I don't think the problem is with the CSS, but here it is:
<style>
// styling for D3 chart
.chart {
background: #fdfefe;
}
.main text {
font: 10px sans-serif;
}
// styling for D3-lasso
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle {
fill-opacity: 0.4;
}
.dot {
stroke: #000;
}
.lasso path {
stroke: rgb(80,80,80);
stroke-width: 2px;
}
.lasso .drawn {
fill-opacity: 0.05 ;
}
.lasso .loop_close {
fill: none;
stroke-dasharray: 4,4;
}
.lasso .origin {
fill: #3399FF;
fill-opacity: 0.5;
}
.not_possible {
fill: rgb(200,200,200);
}
.possible {
fill: #EC888C;
}
.selected {
fill: steelblue;
}
</style>
The problem appears to be that lasso is adding a radius attribute to the title elements here:
lasso.notSelectedItems()
.attr("r", 3.5)
;
resulting in all your not-selected elements, i.e., circles, and titles, having the attribute assigned, as your example suggests:
<title r="3.5" class>
Rather than calling lasso's selected and notSelected to change the radius and css class of the desired items, use a filter on the items array itself:
// Style the selected dots
lasso.items().filter(function(d) {return d.selected===true})
.classed(...)
.attr("r",7);
// Reset the style of the not selected dots
lasso.items().filter(function(d) {return d.selected===false})
.classed(...)
.attr("r",3.5);
You can get as specific as you want with the return value, i.e., omit any nodes (like title nodes) you don't want affected by the rules you apply to the selection.
The problem was that I couldn't get D3 lasso and my approach to tool tips to work together. I was appending a title element to each circle (point) on a scatter plot. This does NOT work:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
.append("title")
.text(function(d) {
return "curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3];
})
;
I found a coding example by Mikhail Shabrikov that solved the issue by avoiding .append("title") altogether. This works:
A new CSS element:
.tooltip {
position: absolute;
z-index: 10;
visibility: hidden;
background-color: lightblue;
text-align: center;
padding: 4px;
border-radius: 4px;
font-weight: bold;
color: black;
}
A new DIV element:
var tooltip = d3.select("body")
.append("div")
.attr('class', 'tooltip');
And mainly a modified circles element:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", 5)
.on("mouseover", function(d) {return tooltip.style("visibility", "visible")
.text(
"curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3]
)
})
.on("mousemove", function() {
return tooltip.style("top", (event.pageY - 30) + "px")
.style("left", event.pageX + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
})
;
Shabrikov's code is near the very bottom of this item: circles, tool tips, mouse events
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)
}
});
}
I'm quite new to HTML and JavaScript. I'm facing the famous Hierarchical Edge Bundling available here, which is generated by the D3.js library.
My goal is to add a semi-circular label zone in order to obtain something like this: every final node group is labelled with the parent's name.
Unfortunately, I have not found any code where I could take inspiration yet, except the code available in the link above: my idea would be to modify that code adding some line in order to generate the labels.
I saw this link with a snippet of code that may do to the trick, but I don't know how to use it (and whether I am in the right direction or not)
node.append("text")
.attr("dy", ".31em")
.attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; })
.style("text-anchor", function(d) { return d.x < 180 === !d.children ? "start" : "end"; })
.attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90 : d.x + 90) + ")"; })
.text(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1); });
Does someone have any suggestion?
The basic idea is to draw a series of arcs around the links and shunt the labels outwards by the width of the arc.
V4 solution
A working adaption of the d3 v4 code from the linked block is below:
var flare = "https://gist.githubusercontent.com/robinmackenzie/d01d286d9ac16b474a2a43088c137d00/raw/c53c1eda18cc21636ae52dfffa3e030295916c98/flare.json";
d3.json(flare, function(err, json) {
if (err) throw err;
render(json);
});
function render(classes) {
// original code
var diameter = 960,
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.cluster()
.size([360, innerRadius]);
var line = d3.radialLine()
.curve(d3.curveBundle.beta(0.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");
var root = packageHierarchy(classes)
.sum(function(d) { return d.size; });
cluster(root);
// added code -----
var arcInnerRadius = innerRadius;
var arcWidth = 30;
var arcOuterRadius = arcInnerRadius + arcWidth;
var arc = d3.arc()
.innerRadius(arcInnerRadius)
.outerRadius(arcOuterRadius)
.startAngle(function(d) { return d.st; })
.endAngle(function(d) { return d.et; });
var leafGroups = d3.nest()
.key(function(d) { return d.parent.data.name.split(".")[1]; })
.entries(root.leaves())
var arcAngles = leafGroups.map(function(group) {
return {
name: group.key,
min: d3.min(group.values, function(d) { return d.x }),
max: d3.max(group.values, function(d) { return d.x })
}
});
svg
.selectAll(".groupArc")
.data(arcAngles)
.enter()
.append("path")
.attr("id", function(d, i) { return`arc_${i}`; })
.attr("d", function(d) { return arc({ st: d.min * Math.PI / 180, et: d.max * Math.PI / 180}) }) // note use of arcWidth
.attr("fill", "steelblue");
svg
.selectAll(".arcLabel")
.data(arcAngles)
.enter()
.append("text")
.attr("x", 5) //Move the text from the start angle of the arc
.attr("dy", (d) => ((arcOuterRadius - arcInnerRadius) * 0.8)) //Move the text down
.append("textPath")
.attr("class", "arcLabel")
.attr("xlink:href", (d, i) => `#arc_${i}`)
.text((d, i) => d.name)
.style("font", `300 14px "Helvetica Neue", Helvetica, Arial, sans-serif`)
.style("fill", "#fff");
// ----------------
link = link
.data(packageImports(root.leaves()))
.enter().append("path")
.each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
.attr("class", "link")
.attr("d", line);
node = node
.data(root.leaves())
.enter().append("text")
.attr("class", "node")
.attr("dy", "0.31em")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8 + arcWidth) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.text(function(d) { return d.data.key; })
.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; })
.raise();
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);
}
// 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 d3.hierarchy(map[""]);
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.data.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.data.imports) d.data.imports.forEach(function(i) {
imports.push(map[d.data.name].path(map[i]));
});
});
return imports;
}
}
.node {
font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #bbb;
}
.node:hover {
fill: #000;
}
.link {
stroke: steelblue;
stroke-opacity: 0.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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
You can see the discrete block of code added to draw and label the arcs - the key bit to calculate the start and end angles for the arc generator are:
var leafGroups = d3.nest()
.key(function(d) { return d.parent.data.name.split(".")[1]; })
.entries(root.leaves())
var arcAngles = leafGroups.map(function(group) {
return {
name: group.key,
min: d3.min(group.values, function(d) { return d.x }),
max: d3.max(group.values, function(d) { return d.x })
}
});
For leafGroups, the nest function is grouping the leaves of the hierarchy by the second item of the key e.g. flare.analytics.cluster = analytics and flare.vis.operator.distortion = vis. There is a choice here for different data sets that you need to have a think about e.g. if the leaves are always at a consistent depth; are the labels always unique. Defining the 'parent group' can either be a top-down or bottom-up definition.
For arcAngles, you just need the min and max of each group then you can go ahead and draw the arcs and label them. I lifted some of the labelling from here which is a great article on labelling arcs in d3. You need to have a think, again, for this bit because if the label is too long for the arc it doesn't look great - see the "Display" label in the example.
The other change is further down here:
node = node
.data(root.leaves())
.enter().append("text")
.attr("class", "node")
.attr("dy", "0.31em")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8 + arcWidth) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.text(function(d) { return d.data.key; })
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);
Noting you need to add arcWidth when setting the transform attribute - this moves the node labels outward to accommodate the arcs.
V6+ solution
There's a newer version of hierarchical edge bundling in this Observable HQ page using d3 v6 (and also by Mike Bostok). We can add similar code to identify groups, get the min/ max of the angles and push the labels outward a bit to accomodate the arcs.
const flare = "https://gist.githubusercontent.com/robinmackenzie/d01d286d9ac16b474a2a43088c137d00/raw/c53c1eda18cc21636ae52dfffa3e030295916c98/flare.json";
const colorin = "#00f";
const colorout = "#f00";
const colornone = "#ccc";
const width = 960;
const radius = width / 2;
d3.json(flare).then(json => render(json));
function render(data) {
const line = d3.lineRadial()
.curve(d3.curveBundle.beta(0.85))
.radius(d => d.y)
.angle(d => d.x);
const tree = d3.cluster()
.size([2 * Math.PI, radius - 100]);
const root = tree(bilink(d3.hierarchy(hierarchy(data))
.sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.name, b.data.name))));
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", width)
.append("g")
.attr("transform", `translate(${radius},${radius})`);
// NEW CODE BELOW ----------------------------------------
// add arcs with labels
const arcInnerRadius = radius - 100;
const arcWidth = 30;
const arcOuterRadius = arcInnerRadius + arcWidth;
const arc = d3
.arc()
.innerRadius(arcInnerRadius)
.outerRadius(arcOuterRadius)
.startAngle((d) => d.start)
.endAngle((d) => d.end);
const leafGroups = d3.groups(root.leaves(), d => d.parent.data.name);
const arcAngles = leafGroups.map(g => ({
name: g[0],
start: d3.min(g[1], d => d.x),
end: d3.max(g[1], d => d.x)
}));
svg
.selectAll(".arc")
.data(arcAngles)
.enter()
.append("path")
.attr("id", (d, i) => `arc_${i}`)
.attr("d", (d) => arc({start: d.start, end: d.end}))
.attr("fill", "blue")
.attr("stroke", "blue");
svg
.selectAll(".arcLabel")
.data(arcAngles)
.enter()
.append("text")
.attr("x", 5) //Move the text from the start angle of the arc
.attr("dy", (d) => ((arcOuterRadius - arcInnerRadius) * 0.8)) //Move the text down
.append("textPath")
.attr("class", "arcLabel")
.attr("xlink:href", (d, i) => `#arc_${i}`)
.text((d, i) => ((d.end - d.start) < (6 * Math.PI / 180)) ? "" : d.name); // 6 degrees min arc length for label to apply
// --------------------------------------------------------
// add nodes
const node = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y}, 0)`)
.append("text")
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI ? (arcWidth + 5) : (arcWidth + 5) * -1) // note use of arcWidth
.attr("text-anchor", d => d.x < Math.PI ? "start" : "end")
.attr("transform", d => d.x >= Math.PI ? "rotate(180)" : null)
.text(d => d.data.name)
.each(function(d) { d.text = this; })
.on("mouseover", overed)
.on("mouseout", outed)
.call(text => text.append("title").text(d => `${id(d)} ${d.outgoing.length} outgoing ${d.incoming.length} incoming`));
// add edges
const link = svg.append("g")
.attr("stroke", colornone)
.attr("fill", "none")
.selectAll("path")
.data(root.leaves().flatMap(leaf => leaf.outgoing))
.join("path")
.style("mix-blend-mode", "multiply")
.attr("d", ([i, o]) => line(i.path(o)))
.each(function(d) { d.path = this; });
function overed(event, d) {
link.style("mix-blend-mode", null);
d3.select(this).attr("font-weight", "bold");
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", colorin).raise();
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", colorin).attr("font-weight", "bold");
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", colorout).raise();
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", colorout).attr("font-weight", "bold");
}
function outed(event, d) {
link.style("mix-blend-mode", "multiply");
d3.select(this).attr("font-weight", null);
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", null).attr("font-weight", null);
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", null).attr("font-weight", null);
}
function id(node) {
return `${node.parent ? id(node.parent) + "." : ""}${node.data.name}`;
}
function bilink(root) {
const map = new Map(root.leaves().map(d => [id(d), d]));
for (const d of root.leaves()) d.incoming = [], d.outgoing = d.data.imports.map(i => [d, map.get(i)]);
for (const d of root.leaves()) for (const o of d.outgoing) o[1].incoming.push(o);
return root;
}
function hierarchy(data, delimiter = ".") {
let root;
const map = new Map;
data.forEach(function find(data) {
const {name} = data;
if (map.has(name)) return map.get(name);
const i = name.lastIndexOf(delimiter);
map.set(name, data);
if (i >= 0) {
find({name: name.substring(0, i), children: []}).children.push(data);
data.name = name.substring(i + 1);
} else {
root = data;
}
return data;
});
return root;
}
}
.node {
font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #fff;
}
.arcLabel {
font: 300 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #fff;
}
.node:hover {
fill: #000;
}
.link {
stroke: steelblue;
stroke-opacity: 0.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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
Some differences to note:
The hierarchy function differs from the packageHierarchy function in the original block - seemingly we no longer have the full path of the hierarchy and therefore there's ambiguity for flare.vis.data.EdgeSprite vs flare.data.DataField i.e. two leaves can have the same 'parent' in different branches of the hierarchy.
I've fixed the input to accomodate that but it changes how the 'parent group' is identified i.e. bottom-up vs top-down in the original.
nest has gone so you can use groups instead
The v4 seems to have objects defined with angles in degrees, but in v6 they are in radians - so you will see a few * Math.PI / 180 in the v4 version and not in the v6 - but it's just degrees/ radians conversion.
for long labels, I use a threshold such that an arc has to be minimum 6 degrees long otherwise the label won't place (.text((d, i) => ((d.end - d.start) < (6 * Math.PI / 180)) ? "" : d.name);)
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
I am very new to d3 and svg, this may sound very naive.
I have one force layout graph and one Circle pack and want to show them both on one page side by side. I went to the "Multiple layouts on same page (multiple force layouts same page)" questions but not able to understand how can I put these layout in div element.
This one is Force layout -
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
h3{
border: 1px solid green ;
}
.link {
stroke: #666;
stroke-opacity: 0.1;
stroke-width: 1.5px;
}
.node circle {
stroke: #fff;
opacity: 0.9;
stroke-width: 1.5px;
}
.node:not(:hover) .nodetext {
display: none;
}
text {
font: 17px serif;
opacity: 0.9;
pointer-events: none;
fill : red;
}
</style>
<script src=http://d3js.org/d3.v3.min.js></script>
<script>
var links= [];
var nodes= [];
var width = 800
height = 400;
var color = d3.scale.category20();
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkStrength(0.1)
.linkDistance(150)
.charge(-150)
.friction(0.6)
.gravity(0.5);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("sample1.json", function(error, data) {
nodes = data.nodes;
links = data.links;
force
.nodes(nodes)
.links(links)
.on("tick", tick)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(1); });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", "#7a85ec")
.style("opacity", 0.9)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click",clickf)
.call(force.drag);
node.append("circle")
.attr("r", function(d) { return Math.sqrt(3*d.weight); })
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", "1.35em")
.attr("dy", "-1.35em")
.text(function(d) { return d.name });
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function mouseover(d) {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", function(o) {
return isConnected(o, d) ? 1.0 : 0.1 ;
})
.style("fill", function(o){
if (isEqual(o, d)){
return "red"
}
else return "#7a85ec"
})
;
link
.transition(500)
.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : 0.1;
})
;
}
function mouseout() {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", "1.0")
.style("fill", "#7a85ec")
;
link
.transition(500)
.style("stroke-opacity", "0.1");
}
function clickf(d){
}
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
});
</script>
</body>
This is my circle pack (sample from mbostock) -
<!DOCTYPE html>
<meta charset="utf-8">
<style>
circle {
fill: rgb(31, 119, 180);
fill-opacity: .25;
stroke: rgb(31, 119, 180);
stroke-width: 1px;
}
.leaf circle {
fill: #ff7f0e;
fill-opacity: 1;
}
text {
font: 10px sans-serif;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var diameter = 560,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("flare.json", function(error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
Please help me put these in one page using div.
This worked for me with the changes below. Hope this helps.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#left-div
{
width:560px;
height:560px;
border:1px solid red;
margin:10px;
display:table-cell;
}
#right-div
{
width:560px;
height:560px;
border:1px solid red;
margin:10px;
display:table-cell;
}
circle {
fill: rgb(31, 119, 180);
fill-opacity: .25;
stroke: rgb(31, 119, 180);
stroke-width: 1px;
}
.leaf circle {
fill: #ff7f0e;
fill-opacity: 1;
}
text {
font: 10px sans-serif;
}
</style>
<div id="left-div"></div>
<div id="right-div"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<body>
<script>
var diameter = 560,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("#left-div").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("flare.json", function(error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
<script>
var links= [];
var nodes= [];
var width = 500
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkStrength(0.1)
.linkDistance(150)
.charge(-150)
.friction(0.6)
.gravity(0.5);
var svg2 = d3.select("#right-div").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("sample1.json", function(error, data) {
nodes = data.nodes;
links = data.links;
force
.nodes(nodes)
.links(links)
.on("tick", tick)
.start();
var link = svg2.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(1); });
var node = svg2.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", "#7a85ec")
.style("opacity", 0.9)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click",clickf)
.call(force.drag);
node.append("circle")
.attr("r", function(d) { return Math.sqrt(3*d.weight); })
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", "1.35em")
.attr("dy", "-1.35em")
.text(function(d) { return d.name });
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function mouseover(d) {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", function(o) {
return isConnected(o, d) ? 1.0 : 0.1 ;
})
.style("fill", function(o){
if (isEqual(o, d)){
return "red"
}
else return "#7a85ec"
})
;
link
.transition(500)
.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : 0.1;
})
;
}
function mouseout() {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", "1.0")
.style("fill", "#7a85ec")
;
link
.transition(500)
.style("stroke-opacity", "0.1");
}
function clickf(d){
}
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
});
</script>
Note, some of the CSS was left out from one of the graphs. Adjust that as needed. The key to getting the d3 graph to load into a div is by this code below. Notice the # within the string name of the div id.
var svg = d3.select("#left-div").append("svg")
Instead of this code below.
var svg = d3.select("body").append("svg")
The next issue that I ran into was due to a duplicate var named "svg" between both scripts, so I renamed one of them to svg2.