I just got started with D3's forced layout and implemented a radial graph implementation by referring : http://flowingdata.com/2012/08/02/how-to-make-an-interactive-network-visualization/
The problem I am facing is that the zooming is not working. Here's the code I referred: http://jsfiddle.net/blt909/aVhd8/20/ for zoom functionality
And here's my code:
var w = $("#network-plot").width(),
h = 800,
r = 6,
fill = d3.scale.category20();
var payload={js:'mis1'}
$.ajax({
url: "/getSource",
type: "post",
async: false,
data: payload,
success: function (data) {
mis1=JSON.parse(data);
},
});
var force = d3.layout.force()
.charge(-30)
.linkDistance(310)
.size([w, h]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svgnt = d3.select("#network-plot-svg")
.attr("width", w)
.attr("height", h)
.style('margin-top', h/15)
.call(zoom);
var vis = svgnt.append("svg:g");
var rect = vis.append("svg:rect")
.attr("width", w)
.attr("height", h)
.attr("fill", '#fff')
.style("pointer-events", "all");
function zoomed() {
vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var radius = d3.scale.log();
var mis1;
//**********RADIAL CODE****************
var unsortednodes = {};
var sortednodes = [];
var sortagainst = [];
var heaviest_node_length = 0;
for(var i = 0; i < mis1.nodes.length; i++) {
var count_conn = 0;
var heaviest_node;
for(var j = 0; j < mis1.links.length; j++) {
if(mis1.links[j].source == mis1.nodes[i].group || mis1.links[j].target == mis1.nodes[i].group) {
count_conn++;
}
}
if(count_conn > heaviest_node_length) {
heaviest_node_length = count_conn;
heaviest_node = i;
}
unsortednodes[i] = 10 + count_conn;
sortagainst[i] = 10 + count_conn;
}
sortagainst.sort();
sortagainst.reverse();
for(i in unsortednodes) {
var index = sortagainst.indexOf(unsortednodes[i]);
sortednodes[index] = i;
sortagainst[index] = '';
}
var c = {"x":w/2, "y":h/2};
var negativeY = 0;
var positiveY = 0;
var negativeX = 0;
var positiveX = 0;
var groupcenters = radialPlacement(c, 300, 18, sortednodes);
//following lines for readjustment code in case svg container is outgrown by graph
h = Math.abs(negativeY) + Math.abs(positiveY) + 100; //TBD: extra padding needs to be dynamic
w = Math.abs(negativeX) + Math.abs(positiveX) + 200;
c = {"x":w/2, "y":h/2};
groupcenters = radialPlacement(c, 300, 18, sortednodes);
svgnt.attr("height", h);
vis.attr("height", h);
svgnt.attr("width", w);
vis.attr("width", w);
function radialLocation(center, angle, radius) {
x = (center.x + radius * Math.cos(angle * Math.PI / 180));
y = (center.y + radius * Math.sin(angle * Math.PI / 180));
if(y < negativeY) {
negativeY = y;
}
if(y > positiveY) {
positiveY = y;
}
if(x < negativeX) {
negativeX = x;
}
if(x > positiveX) {
positiveX = x;
}
return {"x":x,"y":y};
}
function radialPlacement(center, radius, increment, keys) {
var values_circle = {};
var increment;
var start = -90;
var current = start;
var total_nodes = keys.length;
var circles = Math.floor(Math.sqrt((total_nodes - 1)/5));
if(circles == 0) {
circles = 1;
}
var ratio;
var r = [];
var circleKeys = [];
var radius = 140;
r[0] = radius;
var sum_r = r[0];
for(var j = 1; j < circles; j++){
r[j] = r[j-1] + 100 //TBD: should radius be linearly incremented like this?
sum_r += r[j];
}
ratio = 1/sum_r;
var temp = 0;
for(j = 0; j < circles; j++) {
if(j == circles - 1)
circleKeys[j] = total_nodes - temp;
else {
circleKeys[j] = Math.floor(total_nodes * r[j] * ratio);
temp += circleKeys[j];
}
}
var k = 0;
for(var i = 0; i < circleKeys.length; i++) {
increment = 360/circleKeys[i];
for(j = 0; j < circleKeys[i]; j++, k++) {
if(k == 0) {
values_circle[keys[k]] = radialLocation(center, -90, 0);
}
else {
values_circle[keys[k]] = radialLocation(center, current, r[i]);;
current += increment;
}
}
}
return values_circle;
}
//************RADIAL CODE ENDS***************
d3.json(mis1, function() {
var link = svgnt.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = svgnt.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
.attr("r", function(d, j){
var count = 0;
for(var i=0; i<mis1.links.length; i++) {
if(mis1.links[i].source == d.group || mis1.links[i].target == d.group){
count ++;
}
}
return (10+count);
})
.style("fill", function(d) {
return fill(d.group);
})
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
texts = svgnt.selectAll("text.label")
.data(mis1.nodes)
.enter().append("text")
.attr("class", "label")
.attr("fill", "black")
.text(function(d) { return d.name; });
force.nodes(mis1.nodes).links(mis1.links).on("tick", tick).start();
var linkedByIndex = {};
mis1.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function tick() {
node.attr("cx", function(d, i) {
return d.x = groupcenters[i].x;
}).attr("cy", function(d, i) {
return d.y = groupcenters[i].y;
});
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;
});
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
Any idea what I am doing wrong? The graph is displayed fine but doesn't have any zoom capability.
The problem is, you are appending all nodes and links directly to the svg and not to the g element. Since zoom transformations are applied to the vis (g element), nodes and links will not zoom/pan.
So instead of below code
var link = svgnt.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = svgnt.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
----------------------
----------------------
----------------------
try this code.
var link = vis.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = vis.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
----------------------
----------------------
----------------------
Related
Update method : Thanks in advance. I am creating rectangles based on the api response data. The rectangles will will be removed and re-created when the new data come back from api. I want to achieve same thing for adding text inside the rectangles, means as soon as I receive the fresh data text should be overrided or re-created based on the data.
/* update selection*/
var rectangles = vis.ganttSvgRef.selectAll("rect").data(chartData);
/*exit selection*/
rectangles.exit().remove();
/*enter selection*/
var innerRects = rectangles.enter().append("rect").merge(rectangles)
.attr("x", function (d) {
return vis.timeScale(parseTime(d.arrivalTime_data)) + sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot);
}
}
})
.attr("width", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
})
.attr("height", barHeight)
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < vesselsNames.length; i++) {
return serviceColorSelector[d.serviceName_data]
}
})
how to add the text in the rectangles in middle, which should also get update based on the data receive. Thanks
Updated code as suggested by #Michael Rovinsky : This code works perfectly fine for appending the text inside the rect, but on few rect the text is overflowing outside the rect area. I don't want to show the text if it overflow from rect area or how can i hide the text if it overflow from rect area ?
var rectangles = vis.ganttSvgRef.selectAll("rect")
.data(chartData);
rectangles.exit().remove();
var innerRects = rectangles.enter().append("g");
let rectinst = innerRects.append("rect").merge(rectangles)
.attr("x", function (d) {
return vis.timeScale(parseTime(d.arrivalTime_data)) +
sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot);
}
}
})
.attr("width", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
})
.attr("height", barHeight)
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < vesselsNames.length; i++) {
return serviceColorSelector[d.serviceName_data]
}
})
let text = vis.ganttSvgRef.selectAll(".rect-text")
.data(chartData);
text.exit().remove();
innerRects.append("text").merge(text)
.attr("class", 'rect-text')
.text(function (d) {
let rectWidth = (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
console.log("rect width : ", rectWidth)
console.log("d.vesselName_data : ",
vis.timeScale(d.vesselName_data.length))
return d.vesselName_data;
})
.attr("x", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data))) / 2 +
vis.timeScale(parseTime(d.arrivalTime_data)) + sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot) + (barHeight / 2);
}
}
})
.attr("font-size", barHeight / 2)
.attr("text-anchor", "middle")
.attr("text-height", barHeight)
.attr("fill", '#fff');
Append "g" instead of "rect" on enter():
const containers = rectangles.enter().append("g");
Append "rect" and "text" under "g":
containers.append("rect").attr('width', ...).attr('height', ...)...
containers.append("text").text('My Text Here')...
You can write your own text wrapping function d3 based on the rect dimensions and given padding.
Please check following link for customised text wrapping and overflow control in d3.js-
Code- text-wrapping-in-d3
function wrap(text) {
text.each(function() {
var text = d3.select(this);
var words = text.text().split(/\s+/).reverse();
var lineHeight = 20;
var width = parseFloat(text.attr('width'));
var y = parseFloat(text.attr('y'));
var x = text.attr('x');
var anchor = text.attr('text-anchor');
var tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('text-anchor', anchor);
var lineNumber = 0;
var line = [];
var word = words.pop();
while (word) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
lineNumber += 1;
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = text.append('tspan').attr('x', x).attr('y', y + lineNumber * lineHeight).attr('anchor', anchor).text(word);
}
word = words.pop();
}
});
}
function dotme(text) {
text.each(function() {
var text = d3.select(this);
var words = text.text().split(/\s+/);
var ellipsis = text.text('').append('tspan').attr('class', 'elip').text('...');
var width = parseFloat(text.attr('width')) - ellipsis.node().getComputedTextLength();
var numWords = words.length;
var tspan = text.insert('tspan', ':first-child').text(words.join(' '));
// Try the whole line
// While it's too long, and we have words left, keep removing words
while (tspan.node().getComputedTextLength() > width && words.length) {
words.pop();
tspan.text(words.join(' '));
}
if (words.length === numWords) {
ellipsis.remove();
}
});
}
d3.selectAll('.wrapme').call(wrap);
d3.selectAll('.dotme').call(dotme);
I am working on a city to city migration visualization and I am stuck with a problem... I want to display my map on top of the leaflet basemap. The problem is that the leaflet map zoom in and out but the SVG created is static. I tried implementing a lot of examples but I am stuck... Here is my code:
var map = L.map('map', {
center: [52.52, 13.5],
zoom: 11,
zoomControl: true
});
mapLink = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMapcontributors'
}).addTo(map);
// Add an SVG element to Leaflet’s overlay pane
var w = 950;
var h = 600;
var centered;
var formatC = d3.format(",.0f");
var formatD = d3.format("+,.0f");
var immin, immax, exmin, exmax;
var colors = ["#EDF8FB", "#41083E"];
var immdomain = [24431, 537148];
var emmdomain = [20056, 566986];
var circleSize = d3.scale.linear().range([0, 25000]).domain([0, 137175]);
var lineSize = d3.scale.linear().range([2, 25]).domain([0, 35000]);
var fillcolor = d3.scale.linear().range(colors).domain(immdomain);
//Define path generator
var path = d3.geo.path()
//Create SVG element
var svg = d3.select(map.getPanes().overlayPane)
.append("svg")
.attr("width", w)
.attr("height", h)
.style("class", "background");
var fp = d3.format(".1f");
var g = svg.append("g").attr("class", "leaflet-zoom-hide");
// This is the function that transforms D3 svg output to the correct leaflet projection
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
//initialize html tooltip
var tooltip = d3.select("#maincontainer")
.append("div")
.attr("id", "tt")
.style("z-index", "10")
.style("position", "absolute")
.style("visibility", "hidden");
var tooltip2 = d3.select("#maincontainer")
.append("div")
.attr("id", "tt2")
.style("z-index", "10")
.style("position", "absolute")
.style("visibility", "hidden");
var coming, going;
d3.csv("coming.csv", function(data) {
coming = data;
});
d3.csv("going.csv", function(data) {
going = data;
d3.json("test.json", function(json) {
var bounds = d3.geo.bounds(json)
bottomLeft = bounds[0],
topRight = bounds[1],
rotLong = -(topRight[0]+bottomLeft[0])/2;
center = [(topRight[0]+bottomLeft[0])/2+rotLong, (topRight[1]+bottomLeft[1])/2],
//default scale projection
projection = d3.geo.albers()
.parallels([bottomLeft[1],topRight[1]])
.rotate([rotLong,0,0])
.translate([w/2,h/2])
.center(center),
bottomLeftPx = projection(bottomLeft),
topRightPx = projection(topRight),
scaleFactor = 1.00*Math.min(w/(topRightPx[0]-bottomLeftPx[0]), h/(-topRightPx[1]+bottomLeftPx[1])),
projection = d3.geo.albers()
.parallels([bottomLeft[1],topRight[1]])
.rotate([rotLong,0,0])
.translate([w/2,h/2])
.scale(scaleFactor*0.975*1000)
.center(center);
path = d3.geo.path()
.projection(projection);
for (var i = 0; i < data.length; i++) {
var dataName = data[i].state;
var tempObj = {};
for (var propt in data[i]) {
var valz = parseFloat(data[i][propt]);
tempObj[propt] = valz;
}
//Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataName == jsonState) {
matched = true;
json.features[j].properties.state = dataName;
json.features[j].id = dataName;
json.features[j].abbrev = data[i].abbrev;
json.features[j].ind = i;
for (var propt in tempObj) {
if (!isNaN(tempObj[propt])) {
json.features[j].properties[propt] = tempObj[propt];
}
}
break;
}
}
}
//Bind data and create one path per GeoJSON feature
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("class", "state")
.attr("id", function(d) {
return d.properties.state;
})
.attr("d", path)
.attr("stroke-width", 0.5)
.style("stroke", "#666")
.style("fill", "#fff");
g.selectAll("circle")
.data(json.features)
.enter().append("circle")
.attr("cx", function(d) {
var centname = d.properties.name;
var ctroid;
ctroid = path.centroid(d)[0];
return ctroid;
})
.attr("cy", function(d) {
var centname = d.properties.name;
var ctroid;
ctroid = path.centroid(d)[1];
return ctroid;
})
.attr("r", function(d) {
var diff = d.properties.total_imm - d.properties.total_emm;
return circleSize(Math.sqrt(Math.abs(diff) / Math.PI));
})
.attr("class", "circ")
.attr("id", function(d) {
return d.abbrev;
})
.attr("fill", function(d) {
var diff = d.properties.total_imm - d.properties.total_emm;
if (diff > 0) {
return "#65a89d";
}
else {
return "#a96a46";
}
})
.attr("fill-opacity", "0.5")
.attr("stroke", "#fff")
.attr("stroke-weight", "0.5")
.on("mouseover", function(d) {
return toolOver(d, this);
})
.on("mousemove", function(d) {
var m = d3.mouse(this);
mx = m[0];
my = m[1];
return toolMove(mx, my, d);
})
.on("mouseout", function(d) {
return toolOut(d, this);
})
.on("click", function(d) {
clicked(d)
});
});
});
function toolOver(v, thepath) {
d3.select(thepath).style({
"fill-opacity": "0.7",
"cursor": "pointer"
});
return tooltip.style("visibility", "visible");
};
function toolOut(m, thepath) {
d3.select(thepath).style({
"fill-opacity": "0.5",
"cursor": ""
});
return tooltip.style("visibility", "hidden");
};
function toolMove(mx, my, data) {
if (mx < 120) {
mx = 120
};
if (my < 40) {
my = 40
};
return tooltip.style("top", my + -140 + "px").style("left", mx - 120 + "px").html("<div id='tipContainer'><div id='tipLocation'><b>" + data.id + "</b></div><div id='tipKey'>Migration in: <b>" + formatC(data.properties.total_imm) + "</b><br>Migration out: <b>" + formatC(data.properties.total_emm) + "</b><br>Net migration: <b>" + formatC((data.properties.total_imm - data.properties.total_emm)) + "</b></div><div class='tipClear'></div> </div>");
};
function toolOver2(v, thepath) {
d3.select(thepath).style({
"opacity": "1",
"cursor": "pointer"
});
return tooltip2.style("visibility", "visible");
};
function toolOut2(m, thepath) {
d3.select(thepath).style({
"opacity": "0.5",
"cursor": ""
});
return tooltip2.style("visibility", "hidden");
};
function toolMove2(mx, my, home, end, v1, v2) {
var diff = v1 - v2;
if (mx < 120) {
mx = 120
};
if (my < 40) {
my = 40
};
return tooltip2.style("top", my + -140 + "px").style("left", mx - 120 + "px").html("<div id='tipContainer2'><div id='tipLocation'><b>" + home + "/" + end + "</b></div><div id='tipKey2'>Migration, " + home + " to " + end + ": <b>" + formatC(v2) + "</b><br>Migration, " + end + " to " + home + ": <b>" + formatC(v1) + "</b><br>Net change, " + home + ": <b>" + formatD(v1 - v2) + "</b></div><div class='tipClear'></div> </div>");
};
function clicked(selected) {
var selname = selected.id;
var homex = path.centroid(selected)[0];
var homey = path.centroid(selected)[1];
g.selectAll(".goingline")
.attr("stroke-dasharray", 0)
.remove()
g.selectAll(".goingline")
.data(going)
.enter().append("path")
.attr("class", "goingline")
.attr("d", function(d, i) {
var abb = d.abbrev;
var finalval = coming[i][selname] - going[i][selname];
var theState = d3.select("#" + abb);
if (!isNaN(finalval)) {
var startx = path.centroid(theState[0][0].__data__)[0];
var starty = path.centroid(theState[0][0].__data__)[1];
if (finalval > 0) {
return "M" + startx + "," + starty + " Q" + (startx + homex) / 2 + " " + (starty + homey) / 1.5 + " " + homex + " " + homey;
}
else {
return "M" + homex + "," + homey + " Q" + (startx + homex) / 2 + " " + (starty + homey) / 2.5 + " " + startx + " " + starty;
}
}
})
.call(transition)
.attr("stroke-width", function(d, i) {
var finalval = coming[i][selname] - going[i][selname];
return lineSize(parseFloat(Math.abs(finalval)));
})
.attr("stroke", function(d, i) {
var finalval = coming[i][selname] - going[i][selname];
if (finalval > 0) {
return "#65a89d";
}
else {
return "#a96a46";
}
})
.attr("fill", "none")
.attr("opacity", 0.5)
.attr("stroke-linecap", "round")
.on("mouseover", function(d){
return toolOver2(d, this);
})
.on("mousemove", function(d, i){
var m = d3.mouse(this);
mx = m[0];
my = m[1];
return toolMove2(mx, my, selname, d.state, coming[i][selname], going[i][selname]);
})
.on("mouseout", function(d) {
return toolOut2(d, this);
});
}
function transition(path) {
path.transition()
.duration(1500)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) {
return i(t);
};
}
I am still very new to d3 js. I am sure my problem is not a big one but really need help on this.
I am working on a D3 chart, but can't get the node labeling right (I would like to put the "size" on a second line, and change all the text to white. For instance, the "Toyota" node would say "Toyota Motor" and on a new line just below, "61.84").
Here is my starting point. I tried to add a pre block with the CSV data so it could run on JSfiddle, but I got stuck.
I know I need to change this code in order to get the data from the pre block instead of the external CSV:
d3.text("./car_companies.csv", function(error, text) {
After that, I need to add a new "node append", something like this:
node.append("revenue")
.style("text-anchor", "middle")
.attr("dy", "1.5em")
.text(function(d) { return d.revenue.substring(0, d.radius / 3); });
http://jsfiddle.net/nick2ny/pqo1x670/4/
Thank you for any ideas.
Not directly related to the question, but to use the <pre> element to hold your data, you have to use:
var text = d3.select("pre").text();
Instead of d3.text().
Back to the question:
For printing those values, you just need:
node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function(d) {
return d.size;
});
Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function:
size: data[node_counter].size,
Here is the code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
text {
font: 10px sans-serif;
}
pre {
display: none;
}
circle {
stroke: #565352;
stroke-width: 1;
}
</style>
<body>
<pre id="data">
Toyota Motor,61.84,Asia,239
Volkswagen,44.54,Europe,124
Daimler,40.79,Europe,104
BMW,35.78,Europe,80
Ford Motor,31.75,America,63
General Motors,30.98,America,60
</pre>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
Array.prototype.contains = function(v) {
for (var i = 0; i < this.length; i++) {
if (this[i] === v) return true;
}
return false;
};
var width = 500,
height = 500,
padding = 1.5, // separation between same-color nodes
clusterPadding = 6, // separation between different-color nodes
maxRadius = 12;
var color = d3.scale.ordinal()
.range(["#0033cc", "#33cc66", "#990033"]);
var text = d3.select("pre").text();
var colNames = "text,size,group,revenue\n" + text;
var data = d3.csv.parse(colNames);
data.forEach(function(d) {
d.size = +d.size;
});
//unique cluster/group id's
var cs = [];
data.forEach(function(d) {
if (!cs.contains(d.group)) {
cs.push(d.group);
}
});
var n = data.length, // total number of nodes
m = cs.length; // number of distinct clusters
//create clusters and nodes
var clusters = new Array(m);
var nodes = [];
for (var i = 0; i < n; i++) {
nodes.push(create_nodes(data, i));
}
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("g").call(force.drag);
node.append("circle")
.style("fill", function(d) {
return color(d.cluster);
})
.attr("r", function(d) {
return d.radius
})
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function(d) {
return d.text;
});
node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function(d) {
return d.size;
});
function create_nodes(data, node_counter) {
var i = cs.indexOf(data[node_counter].group),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {
cluster: i,
radius: data[node_counter].size * 1.5,
text: data[node_counter].text,
size: data[node_counter].size,
revenue: data[node_counter].revenue,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
};
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
</script>
http://jsfiddle.net/damianpadden/a2QpA/397/
trying to add opacity to this d3 force directed graph but failing. could someone point me in the right direction
When i select the node i want it to make the others not connected to fade out.
Adding a filter for labels and values is next if that is possible
this is a newbie question so be kind. I do love this viz and its going to be useful if i can only get this part to work as i see in my head.
thanks code below
var data = {
edges: [{
source: {
id: 0,
label: "from"
},
target: {
id: 1,
label: "to"
},
value: "after you"
}, {
source: {
id: 0,
label: "from"
},
target: {
id: 1,
label: "to"
},
value: "follow my lead"
}, {
source: {
id: 0,
label: "from"
},
target: {
id: 1,
label: "to"
},
value: "over this way"
},
{
source: {
id: 2,
label: "outside"
},
target: {
id: 3,
label: "lonely"
},value:"helloooooo"
}
]
};
function myGraph() {
this.addNode = function(n) {
if (!findNode(n.id)) {
nodes.push({
"id": n.id,
"label": n.label
});
update();
}
};
this.addLink = function(source, target, value) {
links.push({
"source": findNode(source.id),
"target": findNode(target.id),
"value": value
});
update();
};
this.initialize = function() {
data.edges.forEach(function(d) {
graph.addNode(d.source);
graph.addNode(d.target);
graph.addLink(d.source, d.target, d.value);
});
};
var findNode = function(nodeId) {
for (var i in nodes) {
if (nodes[i].id === nodeId) {
return nodes[i];
}
};
};
var countSiblingLinks = function(source, target) {
var count = 0;
for (var i = 0; i < links.length; ++i) {
if ((links[i].source.id == source.id && links[i].target.id == target.id) || (links[i].source.id == target.id && links[i].target.id == source.id))
count++;
};
return count;
};
var getSiblingLinks = function(source, target) {
var siblings = [];
for (var i = 0; i < links.length; ++i) {
if ((links[i].source.id == source.id && links[i].target.id == target.id) || (links[i].source.id == target.id && links[i].target.id == source.id))
siblings.push(links[i].value);
};
return siblings;
};
var w = window.innerWidth - 20,
h = window.innerHeight,
middle = w / 2;
var linkDistance = 300;
var colors = d3.scale.category20();
var svg = d3.select("body")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.style("z-index", -10)
.attr("id", "svg");
svg.append('svg:defs').selectAll('marker')
.data(['end'])
.enter()
.append('svg:marker')
.attr({
'id': "arrowhead",
'viewBox': '0 -5 10 10',
'refX': 22,
'refY': 0,
'orient': 'auto',
'markerWidth': 20,
'markerHeight': 20,
'markerUnits': "strokeWidth",
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#ccc');
var force = d3.layout.force();
var nodes = force.nodes(),
links = force.links();
var update = function() {
var path = svg.selectAll("path.link")
.data(force.links());
path.enter().append("svg:path")
.attr("id", function(d) {
return d.source.id + "-" + d.value + "-" + d.target.id;
})
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)');
path.exit().remove();
var pathInvis = svg.selectAll("path.invis")
.data(force.links());
pathInvis.enter().append("svg:path")
.attr("id", function(d) {
return "invis_" + d.source.id + "-" + d.value + "-" + d.target.id;
})
.attr("class", "invis");
pathInvis.exit().remove();
var pathLabel = svg.selectAll(".pathLabel")
.data(force.links());
pathLabel.enter().append("g").append("svg:text")
.attr("class", "pathLabel")
.append("svg:textPath")
.attr("startOffset", "50%")
.attr("text-anchor", "middle")
.attr("xlink:href", function(d) {
return "#invis_" + d.source.id + "-" + d.value + "-" + d.target.id;
})
.style("fill", "#cccccc")
.style("font-size", 10)
.text(function(d) {
return d.value;
});
var node = svg.selectAll("g.node")
.data(force.nodes());
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 10)
.attr("id", function(d) {
return "Node;" + d.id;
})
.attr("class", "nodeStrokeClass")
.attr("fill", "#0db7ed")
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 20)
.attr("y", ".31em")
.text(function(d) {
return d.label;
});
node.exit().remove();
function arcPath(leftHand, d) {
var x1 = leftHand ? d.source.x : d.target.x,
y1 = leftHand ? d.source.y : d.target.y,
x2 = leftHand ? d.target.x : d.source.x,
y2 = leftHand ? d.target.y : d.source.y,
dx = x2 - x1,
dy = y2 - y1,
dr = Math.sqrt(dx * dx + dy * dy),
drx = dr,
dry = dr,
sweep = leftHand ? 0 : 1;
siblingCount = countSiblingLinks(d.source, d.target)
xRotation = 0,
largeArc = 0;
if (siblingCount > 1) {
var siblings = getSiblingLinks(d.source, d.target);
console.log(siblings);
var arcScale = d3.scale.ordinal()
.domain(siblings)
.rangePoints([1, siblingCount]);
drx = drx / (1 + (1 / siblingCount) * (arcScale(d.value) - 1));
dry = dry / (1 + (1 / siblingCount) * (arcScale(d.value) - 1));
}
return "M" + x1 + "," + y1 + "A" + drx + ", " + dry + " " + xRotation + ", " + largeArc + ", " + sweep + " " + x2 + "," + y2;
}
force.on("tick", function(e) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length,
k = .1 * e.alpha;
while (++i < n) q.visit(collide(nodes[i]));
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
path.attr("d", function(d) {
return arcPath(true, d);
});
pathInvis.attr("d", function(d) {
return arcPath(d.source.x < d.target.x, d);
});
});
force
.charge(-10000)
.friction(0.5)
.linkDistance(linkDistance)
.size([w, h])
.start();
keepNodesOnTop();
}
update();
function collide(node) {
var r = node.radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
}
}
function drawGraph() {
graph = new myGraph();
graph.initialize();
}
drawGraph();
function keepNodesOnTop() {
$(".nodeStrokeClass").each(function(index) {
var gNode = this.parentNode;
gNode.parentNode.appendChild(gNode);
});
function fade(opacity) {
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d,o) ? 1 :opacity;
this.setAttribute('fill-opacity',thisOpacity);
return thisOpacity;
});
}
}; //end of function
}
I am trying to label multi bar chart. I am having issues with labeling. I hoping someone could help me.I am almost there, the labels are not aligned an i have no no idea why.
My chart is available here:
var chart_monthly;
var fundLabel_monthly;
var withSalesChargeFund_monthly;
var woutSalesChargeFund_monthly;
var indexPerformanceData_monthly;
var indexName_monthly;
var displaycase = true;
var yMin, yMax;
var width = 864;
var height = 380;
fundLabel_monthly = $("[id$='_hfAvgReturnFundLabel']").val(); // yields string, e.g. Genesis Fund, Class Trust - NBGEX
var keys = ['Class Trust', 'Class Institutional', 'Class Advisor', 'Class Investor', 'Class R3']
for (i = 0; i < keys.length; i++) {
var patt = new RegExp(keys[i]);
if (patt.test(fundLabel_monthly)) {
displaycase = false; // tells code to swap default to Without Sales Charge data set
break;
}
}
if (displaycase == false) {
$("[id$='monthly_saleschargetoggle']").hide();
withSalesChgFlag = false; //override what is passed in as for non A and C, there are no sales charges.
} else {
$("[id$='monthly_saleschargetoggle']").show();
}
indexName_monthly = $("[id$='_hfPrimaryBenchmarkName']").val();
if (indexName_monthly.length == 0 || indexName_monthly.length == 0)
return;
withSalesChargeFund_monthly = jQuery.parseJSON($("[id$='_hfWithSalesChargeFundMonthly']").val());
woutSalesChargeFund_monthly = jQuery.parseJSON($("[id$='_hfWoutSalesChargeFundMonthly']").val());
indexPerformanceData_monthly = jQuery.parseJSON($("[id$='_hfIndexPerformanceData']").val());
if (withSalesChargeFund_monthly.length == 0 || woutSalesChargeFund_monthly.length == 0 || indexPerformanceData_monthly.length == 0) {
return false;
}
var data = withSalesChgFlag ? getMonthlyDataWithSalesCharge() : getMonthlyDataWoutSalesChart();
nv.addGraph(function () {
chart_monthly = nv.models.multiBarChart()
.rotateLabels(0) //Angle to rotate x-axis labels.
.showControls(false) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1) //Distance between each group of bars.
;
//create an array of all the y data points
var yArray = [];
for (i = 0; i < data[0].values.length; i++) {
yArray.push(data[0].values[i].y);
}
for (i = 0; i < data[1].values.length; i++) {
yArray.push(data[1].values[i].y);
}
var xArray = [];
for (i = 0; i < data[0].values.length; i++) {
xArray.push(data[0].values[i].x);
}
for (i = 0; i < data[1].values.length; i++) {
xArray.push(data[1].values[i].x);
}
yArray = yArray.sort(function(a,b){return a - b});
xArray = xArray.sort(function(a,b){return a - b});
yMax = d3.max(yArray);
yMin = d3.min(yArray);
yMax = Math.ceil(yMax+5);
yMin = Math.ceil(yMin-5);
var y = d3.scale.linear().range([height,0]).domain([yMin,yMax]);
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.1).domain(xArray);
chart_monthly.yAxis.showMaxMin(false);
chart_monthly.xAxis.tickFormat(function (d) {
var index = d.replace(/^\d+_/, '');
if (index < data[0].values.length) {
var label = data[0].values[index].label;
if (typeof label !== 'undefined') {
return label.replace(/(\d\d)(\d\d)$/, '$2').replace(/<br.+$/, '');
} else {
return '/';
}
} else {
return '-';
}
});
chart_monthly.yAxis.tickFormat(function (d) {
return d3.format(",.2f")(d) + '%';
});
chart_monthly.forceY([yMin, yMax]);
debugger;
d3.select('#nd3Chart')
.datum(data)
.call(chart_monthly);
var yTextPadding = 20;
d3.select('#nd3Chart .nv-series-0').selectAll("rect").select("text")
.data(data[0].values)
.enter()
.append("text")
.text(function(d,i) {
console.log(d.y)
if ( d.y == 0.00000001)
return "N/A";
else
return (d.y + "%");
})
.attr('x', function(d) {
//return (x(d.x)+15)
debugger;
console.log(x(i));
return x(i)+x.rangeBand();
}
)
.attr('y', function(d)
{
if (d.y<0)
return (y(d.y)+30);
else
return height-y(d.y)+yTextPadding;
//if (d.y<0)
// return (y(d.y)-30);
//else
// return (y(d.y)-15);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.attr("text-anchor", "middle")
;
//////////////////////////////////////////////////
d3.select('#nd3Chart .nv-series-1').selectAll("rect").select("text")
.data(data[1].values)
.enter()
.append("text")
.text(function(d,i) {
console.log(d.y)
if ( d.y == 0.00000001)
return "N/A";
else
return (d.y + "%");
})
.attr('x', function(d,i) {
return x(i)+x.rangeBand()/2;
})
.attr('y', function(d, i)
{
if (d.y<0)
return (y(d.y)+30);
else
return height-y(d.y)+yTextPadding;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.attr("text-anchor", "middle")
;
nv.utils.windowResize(chart_monthly.update);
});
function getMonthlyDataWithSalesCharge() {
return [
{
"key": fundLabel_monthly,
"color": "#123a5f",
"values": withSalesChargeFund_monthly
},
{
"key": indexName_monthly,
"color": "#56a0d3",
"values": indexPerformanceData_monthly
}
];
}
function getMonthlyDataWoutSalesChart() {
return [
{
"key": fundLabel_monthly,
"color": "#123a5f",
"values": woutSalesChargeFund_monthly
},
{
"key": indexName_monthly,
"color": "#56a0d3",
"values": indexPerformanceData_monthly
}
];
}
Thanks
Alex