Updating D3 bubble radius with collide simulation - javascript

I've been wrestling very hard with D3 to try to make a simple bubble-chart using force collide that live-updates the bubble size.
I can get the chart to show on the first data update with force collide. However subsequent data calls freeze the chart and the sizes are never updated:
https://jsfiddle.net/d2zcfjfa/1/
node = svg.selectAll('g.node')
.data(root.children)
.enter()
.append('g')
.attr('class', 'node')
.append('circle')
.attr('r', function(d) { return d.r * 1.4; })
.attr('fill', function(d) { return color(d.data.name); })
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragged)
.on("end", dragEnd));
var circleUpdate = node.select('circle')
.attr('r', function(d)
{
return d.r;
});
simulation.nodes(root.children);
I can get updates to work but only without using the collide simulation as seen here:
https://jsfiddle.net/rgdox7g7/1/
node = svg.selectAll('g.node')
.data(root.children)
.enter()
.append('g')
.attr('id', function(d) { return d.id; })
.attr('class', 'node')
.attr('transform', function(d)
{
return "translate(" + d.x + "," + d.y + ")";
});
var nodeUpdate = svg.selectAll('g.node')
.transition()
.duration(2000)
.ease(d3.easeLinear);
var circleUpdate = nodeUpdate.select('circle')
.attr('r', function(d)
{
return d.r;
});
node.append("circle")
.attr("r", function(d) { return d.r; })
.style('fill', function(d) { return color(d.data.name); });
Everything I have tried to mix these two solutions together simply does not work. I have scoured the internet for other examples and nothing I can find is helping. Can someone please help me understand what to do? I never thought D3 would be such a frustration!

stackoverflow: the place where you have to answer your own questions.
here is my working solution:
https://jsfiddle.net/zc0fgh6y/
var subscription = null;
var width = 600;
var height = 300;
var maxSpeed = 1000000;
var pack = d3.pack().size([width, height]).padding(0);
var svg = d3.select('svg');
var node = svg.selectAll("g.node");
var root;
var nodes = [];
var first = true;
var scaleFactor = 1.4;
var color = d3.interpolateHcl("#0faac3", "#dd2323");
var forceCollide = d3.forceCollide()
.strength(.8)
.radius(function(d)
{
return d.r;
}).iterations(10);
var simulationStart = d3.forceSimulation()
.force("forceX", d3.forceX(width/2).strength(.04))
.force("forceY", d3.forceY(height/2).strength(.2))
.force('collide', forceCollide)
.on('tick', ticked);
var simulation = d3.forceSimulation()
.force("forceX", d3.forceX(width/2).strength(.0005))
.force("forceY", d3.forceY(height/2).strength(.0025))
.force('collide', forceCollide)
.on('tick', ticked);
function ticked()
{
if (node)
{
node.attr('transform', function(d)
{
return "translate(" + d.x + "," + d.y + ")";
}).select('circle').attr('r', function(d)
{
return d.r;
});
}
}
function rand(min, max)
{
return Math.random() * (max - min) + min;
};
setInterval(function()
{
var hosts = [];
for (var i = 0; i < 100; i++)
{
hosts.push({name: i, cpu: rand(10,100), speed: rand(0,maxSpeed)});
}
root = d3.hierarchy({children: hosts})
.sum(function(d)
{
return d.cpu ? d.cpu : 0;
});
var leaves = pack(root).leaves().map(function(item)
{
return {
id: 'node-'+item.data.name,
name: item.data.name,
r: item.r * scaleFactor,
x: width/2,
y: height/2,
cpu: item.data.cpu,
speed: item.data.speed
};
});
for (var i = 0; i < leaves.length; i++)
{
if (nodes[i] && nodes[i].id == leaves[i].id)
{
var oldR = nodes[i].newR;
nodes[i].oldR = oldR;
nodes[i].newR = leaves[i].r;
nodes[i].cpu = leaves[i].cpu;
nodes[i].speed = leaves[i].speed;
}
else
{
nodes[i] = leaves[i];
//nodes[i].r = 1;
nodes[i].oldR = 1;//nodes[i].r;
nodes[i].newR = leaves[i].r;
}
}
if (first)
{
first = false;
node = node.data(nodes, function(d) { return d.id; });
node = node.enter()
.append('g')
.attr('class', 'node');
node.append("circle")
.style("fill", 'transparent');
node.append("text")
.attr("dy", "0.3em")
.style('fill', 'transparent')
.style("text-anchor", "middle")
.text(function(d)
{
return d.name;//.substring(0, d.r / 4);
});
// transition in size
node.transition()
.ease(d3.easePolyInOut)
.duration(950)
.tween('radius', function(d)
{
var that = d3.select(this);
var i = d3.interpolate(1, d.newR);
return function(t)
{
d.r = i(t);
that.attr('r', function(d)
{
return d.r;
});
simulationStart.nodes(nodes).alpha(1);
}
});
// fade in text color
node.select('text')
.transition()
.ease(d3.easePolyInOut)
.duration(950)
.style('fill', 'white');
// fade in circle size
node.select('circle')
.transition()
.ease(d3.easePolyInOut)
.duration(950)
.style('fill', function(d)
{
return color(d.speed / maxSpeed);
});
}
else
{
// transition to new size
node.transition()
.ease(d3.easeLinear)
.duration(950)
.tween('radius', function(d)
{
var that = d3.select(this);
var i = d3.interpolate(d.oldR, d.newR);
return function(t)
{
d.r = i(t);
that.attr('r', function(d)
{
return d.r;
});
simulation.nodes(nodes).alpha(1);
}
});
// transition to new color
node.select('circle')
.transition()
.ease(d3.easeLinear)
.duration(950)
.style('fill', function(d)
{
return color(d.speed / maxSpeed);
});
}
}, 1000);

Related

D3.JS V4 update chart elements after adding/updating data

I'm trying to build simple line chart using d3.js V4.
I've got basic concept from multiple bl.ocks samples.
My idea is to create char and then add data to it, with max 9 data points.
Here is view of what I've build so far:
I'm able to update line using this:
addValue: function(val) {
chartData.push(val);
if (chartData.length > 9) {
chartData.shift();
}
y.domain([
-2,
d3.max(chartData, function(d) {
return d + 2;
})
]);
var svg = element.transition();
svg
.select(".d3-line")
.duration(750)
.attr("d", valueline(chartData));
}
but I also want to add/move point and lines when I add new data, without this my buggy chart looks like this:
I'm adding initial points and lines using this code:
var lineGuides = svg
.append("g")
.selectAll(".d3-line-guides-group")
.data(chartData);
lineGuides
.enter()
.append("line")
.attr("class", "d3-line-guides")
.attr("x1", function(t, e) {
return x(e);
})
.attr("y1", function(t, a) {
return height;
})
.attr("x2", function(t, e) {
return x(e);
})
.attr("y2", function(t, a) {
return height;
})
.style("stroke", "rgba(255,255,255,0.3)")
.style("stroke-dasharray", "4,2")
.style("shape-rendering", "crispEdges")
.transition()
.duration(1000)
.delay(function(t, x) {
return 150 * x;
})
.attr("y2", function(t) {
return y(t);
})
.transition();
var points = svg
.insert("g")
.selectAll(".d3-line-circle")
.data(chartData)
.enter()
.append("circle")
.attr("class", "d3-line-circle d3-line-circle-medium")
.attr("cx", function(t, e) {
return x(e);
})
.attr("cy", function(t) {
return y(t);
})
.attr("r", 3)
.style("stroke", "#fff")
.style("fill", "#29B6F6")
.on("mouseover", function(t) {
d3
.select(this)
.transition()
.duration(250)
.attr("r", 5);
})
.on("mouseout", function(t) {
d3
.select(this)
.transition()
.duration(250)
.attr("r", 3);
});
points
.style("opacity", 0)
.transition()
.duration(250)
.ease(d3.easeLinear, 2)
.delay(1000)
.style("opacity", 1);
How can I add new points and update old when data changes?
Here is code that I have so far:
/* global window, define, module */
(function(global, factory) {
var MicroChart = factory(global);
if (typeof define === "function" && define.amd) {
// AMD support
define(function() {
return MicroChart;
});
} else if (typeof module === "object" && module.exports) {
// CommonJS support
module.exports = MicroChart;
} else {
// We are probably running in the browser
global.MicroChart = MicroChart;
}
})(typeof window === "undefined" ? this : window, function(global, undefined) {
var document = global.document;
var slice = Array.prototype.slice;
var MicroChart = (function() {
var defaultOptions = {
height: 50
};
function shallowCopy(/* source, ...targets*/) {
var target = arguments[0],
sources = slice.call(arguments, 1);
sources.forEach(function(s) {
for (k in s) {
if (s.hasOwnProperty(k)) {
target[k] = s[k];
}
}
});
return target;
}
return function MicroChart(elem, opts) {
opts = shallowCopy({}, defaultOptions, opts);
var gaugeContainer = elem,
chartHeigh = opts.height,
instance;
var xScale, yScale, valueline, x, y;
var chartData = [5, 8, 2];
var element;
function initializeMicroChart(elem, height) {
element = d3.select(elem);
var margins = { top: 0, right: 0, bottom: 0, left: 0 };
var width =
element.node().getBoundingClientRect().width -
margins.left -
margins.right;
var height = chartHeigh - margins.top - margins.bottom;
var l = 10;
x = d3
.scaleLinear()
.domain([0, 8])
.range([l, width - l]);
y = d3.scaleLinear().range([height, 0]);
valueline = d3
.line()
.x(function(d, i) {
console.log(i);
return x(i);
})
.y(function(d) {
return y(d);
});
var svg = element
.append("svg")
.attr("width", width + margins.left + margins.right)
.attr("height", height + margins.top + margins.bottom);
y.domain([
-2,
d3.max(chartData, function(d) {
return d + 2;
})
]);
var s4 =function() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
var guid = s4()+s4();
console.log(guid);
var path = svg
.append("path")
.data([chartData])
.attr("class", "d3-line d3-line-medium")
.attr("clip-path", "url(#"+guid+")")
.attr("d", valueline)
.style("stroke", "#fff");
var clipPath = svg
.append("defs")
.append("clipPath")
.attr("id", guid);
var rect = clipPath
.append("rect")
.attr("class", "clip")
.attr("width", 0)
.attr("height", height)
.attr("transform", null)
.transition()
.duration(1000)
.ease(d3.easeLinear, 2)
.attr("width", width);
var lineGuides = svg
.append("g")
.selectAll(".d3-line-guides-group")
.data(chartData);
lineGuides
.enter()
.append("line")
.attr("class", "d3-line-guides")
.attr("x1", function(t, e) {
return x(e);
})
.attr("y1", function(t, a) {
return height;
})
.attr("x2", function(t, e) {
return x(e);
})
.attr("y2", function(t, a) {
return height;
})
.style("stroke", "rgba(255,255,255,0.3)")
.style("stroke-dasharray", "4,2")
.style("shape-rendering", "crispEdges")
.transition()
.duration(1000)
.delay(function(t, x) {
return 150 * x;
})
.attr("y2", function(t) {
return y(t);
})
.transition();
var points = svg
.insert("g")
.selectAll(".d3-line-circle")
.data(chartData)
.enter()
.append("circle")
.attr("class", "d3-line-circle d3-line-circle-medium")
.attr("cx", function(t, e) {
return x(e);
})
.attr("cy", function(t) {
return y(t);
})
.attr("r", 3)
.style("stroke", "#fff")
.style("fill", "#29B6F6")
.on("mouseover", function(t) {
d3
.select(this)
.transition()
.duration(250)
.attr("r", 5);
})
.on("mouseout", function(t) {
d3
.select(this)
.transition()
.duration(250)
.attr("r", 3);
});
points
.style("opacity", 0)
.transition()
.duration(250)
.ease(d3.easeLinear, 2)
.delay(1000)
.style("opacity", 1);
}
instance = {
addValue: function(val) {
chartData.push(val);
if (chartData.length > 9) {
chartData.shift();
}
y.domain([
-2,
d3.max(chartData, function(d) {
return d + 2;
})
]);
var svg = element.transition();
svg
.select(".d3-line")
.duration(750)
.attr("d", valueline(chartData));
}
};
initializeMicroChart(gaugeContainer, chartHeigh);
return instance;
};
})();
return MicroChart;
});
var gauge1 = MicroChart(document.getElementById("chart1"));
var gauge2 = MicroChart(document.getElementById("chart2"), {
height: 70
});
var randomInt = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
$("#update").on("click", function() {
gauge1.addValue(randomInt(5, 15));
gauge2.addValue(randomInt(5, 15));
});
And here is codepen to see my code in action: https://codepen.io/Misiu/pen/dmGyZW?editors=0010
Use .enter().append() to add new nodes, .merge() to merge existing and appended nodes, then update all nodes and call .exit().remove() to remove unnecessary nodes. So, you could use following update pattern:
d3.select(window).on('load', function() {
// Join data
var joined = d3.select('div').selectAll('p').data([1, 2, 3, 4, 5]);
joined
// Add new elements
.enter().append('p')
// Merge both new and existing elements
.merge(joined)
// Update new and existing elements
.text(d => d);
// Remove excess elements
joined.exit().remove();
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<div>
<p>a</p>
<p>b</p>
<p>c</p>
</div>
As result, the two <p> elements will be created and all <p> elements will be updated.
See also General Update Pattern, I.

Dynamically update D3 Sunburst if the source json is updated (item added or deleted)

I am new to D3 and trying to dynamically update the chart if the source json is modified. But I am not able to achieve this.
Please check this plunkr
Js:
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ") rotate(-90 0 0)");
var partition = d3.layout.partition()
.value(function(d) {
return d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
//d3.json("/d/4063550/flare.json", function(error, root) {
var root = initItems;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//.append("text")
var text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
function click(d) {
console.log(d)
// fade out all text elements
if (d.size !== undefined) {
d.size += 100;
};
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function(d) {
return y(d.y);
});
}
});
} //});
// Word wrap!
var insertLinebreaks = function(t, d, width) {
alert(0)
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("g")
.attr("x", function(d) {
return y(d.y);
})
// .attr("dx", "6") // margin
//.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
//p
.append("foreignObject")
.attr('x', -width / 2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style', 'word-wrap: break-word; text-align:center;')
.html(d.name);
alert(1)
el.remove();
alert(2)
};
//g.selectAll("text")
// .each(function(d,i){ insertLinebreaks(this, d, 50 ); });
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i ? function(t) {
return arc(d);
} : function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function arcTweenUpdate(a) {
console.log(path);
var _self = this;
var i = d3.interpolate({ x: this.x0, dx: this.dx0 }, a);
return function(t) {
var b = i(t);
console.log(window);
_self.x0 = b.x;
_self.dx0 = b.dx;
return arc(b);
};
}
setTimeout(function() {
path.data(partition.nodes(newItems))
.transition()
.duration(750)
.attrTween("d", arcTweenUpdate)
}, 2000);
In addition to what #Cyril has suggested about removing the following line:
d3.select(self.frameElement).style("height", height + "px");
I made further modifications in your fiddle: working fiddle
The idea used here is to add a function updateChart which takes the items and then generate the chart:
var updateChart = function (items) {
// code to update the chart with new items
}
updateChart(initItems);
setTimeout(function () { updateChart(newItems); }, 2000);
This doesn't use the arcTweenUpdate function you have created but I will try to explain the underlying concept:
First, you will need to JOIN the new data with your existing data:
// DATA JOIN - Join new data with old elements, if any.
var gs = svg.selectAll("g").data(partition.nodes(root));
then, ENTER to create new elements if required:
// ENTER
var g = gs.enter().append("g").on("click", click);
But, we also need to UPDATE the existing/new path and text nodes with new data:
// UPDATE
var path = g.append("path");
gs.select('path')
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
//.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500)
.attr("d", arc);
var text = g.append("text");
gs.select('text')
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
and, after everything is created/updated remove the g nodes which are not being used i.e. EXIT:
// EXIT - Remove old elements as needed.
gs.exit().transition().duration(500).style("fill-opacity", 1e-6).remove();
This whole pattern of JOIN + ENTER + UPDATE + EXIT is demonstrated in following articles by Mike Bostock:
General Update Pattern - I
General Update Pattern - II
General Update Pattern - III
In side the fiddle the setTimeout is not running because:
d3.select(self.frameElement).style("height", height + "px");
You will get Uncaught SecurityError: Failed to read the 'frame' property from 'Window': Blocked a frame with origin "https://fiddle.jshell.net" from accessing a frame with origin and the setTimeout never gets called.
So you can remove this line d3.select(self.frameElement).style("height", height + "px"); just for the fiddle.
Apart from that:
Your timeout function should look like this:
setTimeout(function() {
//remove the old graph
svg.selectAll("*").remove();
root = newItems;
g = svg.selectAll("g")
.data(partition.nodes(newItems))
.enter().append("g");
/make path
path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//make text
text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
}
working fiddle here
for the enter() and transitions to work you need to give d3 a way to identify each item in your data. the .data() function has a second parameter that lets you return something to use as an id. enter() will use the id to decide whether the object is new.
try changing
path.data(partition.nodes(newItems))
.data(partition.nodes(root));
to
path.data(partition.nodes(newItems), function(d){return d.name});
.data(partition.nodes(root), function(d){return d.name});

Add a Legend to D3 Force Directed Graph

So I am trying to create a legend in the bottom right corner of my D3. I have written all of the code for the legend but it comes up as just a black screen with the force-directed graph not showing up as well. Any advice would help.
Legend Code:
var color = d3.scale.ordinal()
.domain(["<400", "400-549", "550-699", "700-849", "850-999", "1000-1149", "1150-1299", "1300-1449", ">1450"])
.range(["#1a9850", "#66bd63", "#a6d96a","#d9ef8b","#ffffbf","#fee08b","#fdae61","#f46d43","#d73027"]);
var legend = d3.append('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 0;
var y = i * height;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
D3 Code:
function start(){
var w = 1200,
h = 600,
radius = 10,
node,
link,
root;
var count = 0;
var color = d3.scale.ordinal()
.domain(["<400", "400-549", "550-699", "700-849", "850-999", "1000-1149", "1150-1299", "1300-1449", ">1450"])
.range(["#1a9850", "#66bd63", "#a6d96a","#d9ef8b","#ffffbf","#fee08b","#fdae61","#f46d43","#d73027"]);
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) { return -500; })
.linkDistance(function(d) { return d.target._children ? 100 : 50; })
.size([w, h - 160]);
`
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#000");
var legend = d3.append('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 0;
var y = i * height;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
root = JSON.parse(jsonObject);
console.log("root"+root);
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = root.nodes,
links = root.links;
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("xlink:href", function(d) { return d.image;})
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.attr("r", function(d)
{return d.size * 2 ;})
.style("fill", color)
.on("click", click)
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
// Exit any old nodes.
node.exit().remove();
title = svg.selectAll("text.title")
.data(nodes);
// Enter any new titles.
title.enter()
.append("text")
.attr("class", "title");
//.text(function(d) { return d.name; });
// Exit any old titles.
title.exit().remove();
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
title.attr("transform", function(d){ return "translate("+d.x+","+d.y+")"; });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if(d._children){
return "#95a5a6";
}else{
switch(d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "rgb(0, 238, 238)";
}
}
}
// Toggle children on click.
function click(d) {
document.getElementById("image").src = d.image;
document.getElementById("username").innerHTML = "Username:"+d.name;
document.getElementById("id").innerHTML = "ID:" + d.id;
document.getElementById("friends").innerHTML = d.friend;
document.getElementById("nodeTitle").innerHTML = "";
document.getElementById("size").innerHTML = d.size;
//document.getElementById("id").innerHTML = "Friend Count:" + d.name;
//if (d._children)
//grabImage();
//document.getElementById("image").innerHTML = (d.image);
/*if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();*/
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}};
do{
var intervalID = window.setTimeout(start, 1000)
}
while(jsonObject!=""){
}
I believe the error is in here somewhere:
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) { return -500; })
.linkDistance(function(d) { return d.target._children ? 100 : 50; })
.size([w, h - 160]);
`
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#000");
var legend = d3.append('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 0;
var y = i * height;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
JSFIDDLE :
jsfiddle.net/d1kp0qeL/1
This is incorrect:
var legend = d3.append('svg').append("g")
should have been (you should append, the g group which holds legend to the svg)
var legend = svg.append("g")

Static Force Directed Graph links not correct length [duplicate]

This question already has answers here:
d3.js linkStrength influence on linkDistance in a force graph
(2 answers)
Closed 2 years ago.
I am trying to create a static force directed graph. One that loads without any animation in. Here's what I'm trying to emulate: http://bl.ocks.org/mbostock/1667139
I have the following D3 graph:
var width = $("#theVizness").width(),
height = $("#theVizness").height();
var color = d3.scale.ordinal().range(["#ff0000", "#fff000", "#ff4900"]);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("#theVizness").append("svg")
.attr("width", width)
.attr("height", height);
var loading = svg.append("text")
.attr("class", "loading")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Loading...");
d3.json("https://dl.dropboxusercontent.com/u/5772230/ForceDirectData.json", function (error, json) {
var nodes = json.nodes;
force.nodes(nodes)
.links(json.links)
.linkDistance(function (d) {
return d.value * 1.5;
})
.friction(0.4);
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
var files = svg.selectAll(".file")
.data(json.nodes)
.enter().append("circle")
.attr("class", "file")
.attr("r", 10)
.attr("fill", function (d) {
return color(d.colorGroup);
});
var totalNodes = files[0].length;
files.append("title")
.text(function (d) { return d.name; });
force.start();
for (var i = totalNodes * totalNodes; i > 0; --i) force.tick();
force.stop();
nodes[0].x = width / 2;
nodes[0].y = height / 2;
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; });
files.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("class", function(d){
var classString = "file"
if (d.index === 0) classString += " rootFile";
return classString;
})
.attr("r", function(d){
var radius = 10;
if (d.index === 0) radius = radius * 2;
return radius;
});
loading.remove();
});
Here's my data: https://dl.dropboxusercontent.com/u/5772230/ForceDirectData.json
{
"nodes":[
{"name":"File1.exe","colorGroup":0},
{"name":"File2.exe","colorGroup":0},
{"name":"File3.exe","colorGroup":0},
{"name":"File4.exe","colorGroup":0},
{"name":"File5.exe","colorGroup":0},
{"name":"File6.exe","colorGroup":0},
{"name":"File7.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File9.exe","colorGroup":0}
],
"links":[
{"source":1,"target":0,"value":10},
{"source":2,"target":0,"value":35},
{"source":3,"target":0,"value":50},
{"source":4,"target":0,"value":50},
{"source":5,"target":0,"value":65},
{"source":6,"target":0,"value":65},
{"source":7,"target":0,"value":81},
{"source":8,"target":0,"value":98},
{"source":9,"target":0,"value":100}
]
}
Fiddle
From my understanding of the bl.ocks page, this graph is running the tick method a certain amount of times. But my issue is the lengths of my links between the nodes are not proportionate to what I have in my JSON file.
I've opted for the static graph because I did not want to have the graph animate in, like in the standard graph.
Why are my links to the nodes nor correctly proportioned to match my JSON file?
I do not understand your question.
Is this what you mean?
var width = $("#theVizness").width(),
height = $("#theVizness").height();
var color = d3.scale.ordinal().range(["#ff0000", "#fff000", "#ff4900"]);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("#theVizness").append("svg")
.attr("width", width)
.attr("height", height);
var loading = svg.append("text")
.attr("class", "loading")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Loading...");
d3.json("https://dl.dropboxusercontent.com/u/5772230/ForceDirectData.json", function (error, json) {
var nodes = json.nodes;
force.nodes(nodes)
.links(json.links)
.linkDistance(function (d) {
return d.value * 1.5;
})
.friction(0.4);
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
var files = svg.selectAll(".file")
.data(json.nodes)
.enter().append("circle")
.attr("class", "file")
.attr("r", 10)
.attr("fill", function (d) {
return color(d.colorGroup);
});
var totalNodes = files[0].length;
files.append("title")
.text(function (d) { return d.name; });
force.start();
for (var i = totalNodes * totalNodes; i > 0; --i) force.tick();
nodes[0].x = width / 2;
nodes[0].y = height / 2;
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; });
files.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("class", function(d){
var classString = "file"
if (d.index === 0) classString += " rootFile";
return classString;
})
.attr("r", function(d){
var radius = 10;
if (d.index === 0) radius = radius * 2;
return radius;
});
force.on("tick", function() {
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; });
files.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
loading.remove();
});
JSFiddle

D3.js Image at corner issue

Hello im trying to implement this D3 project http://bl.ocks.org/929623:
with images like this one http://bl.ocks.org/950642:
But I can't make the source images to resize and move along with the nodes. Heres the code:
var nodesCreated = 1;
var newDistance = 100;
var width = document.documentElement.clientWidth,
height = document.documentElement.clientHeight,
fill = d3.scale.category20(),
nodes = [],
links = [];
var vis = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
vis.append("rect")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.linkDistance(newDistance)
.nodes(nodes)
.links(links)
.gravity(.01)
.size([width, height]);
force.on("tick", function() {
vis.selectAll("line.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; });
vis.selectAll(".node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
var tempX = window.innerWidth/2;
var tempY = window.innerHeight/2;
var point = tempX,tempY,
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/48799_806120304_700633127_n.jpg"};
n = nodes.push(node);
vis.on("mousedown", function() {
var point = d3.mouse(this),
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash4/211698_100002756979859_374256176_n.jpg"},
n = nodes.push(node);
nodesCreated++;
console.log(nodesCreated);
var tempCounter = 0;
newDistance == 10;
force.linkDistance(newDistance);
nodes.forEach(function(target) {
if (/*Math.sqrt(x * x + y * y) < 100 ||*/ tempCounter == 0) {
links.push({source: node, target: target});
tempCounter++;
}
});
restart();
});
function restart() {
force.start();
vis.selectAll("line.link")
.data(links)
.enter().insert("line", ".node")
.attr("class", "link");
var realNode = vis.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
realNode.append("image")
.attr("xlink:href", function(d) { return d.imgsrc; })
.attr("x", -8)
.attr("y", -8)
.attr("width", 160)
.attr("height", 160);
}
I have been looking for some help at google but I found no solution.
You should add X and Y co-ordinates to your nodes:
var tempX = window.innerWidth/2;
var tempY = window.innerHeight/2;
var point = [tempX,tempY],
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/48799_806120304_700633127_n.jpg", x: tempX, y: tempY};
and
var point = d3.mouse(this),
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash4/211698_100002756979859_374256176_n.jpg", x:point[0], y:point[1]},
n = nodes.push(node);
And then need to add a transform to the force.on("tick".... function:
vis.selectAll(".node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ") scale(0.30)"; });
This scales your images down to 30%, but you can configure this.
For completeness, here is all of the code:
var nodesCreated = 1;
var newDistance = 100;
var width = document.documentElement.clientWidth,
height = document.documentElement.clientHeight,
fill = d3.scale.category20(),
nodes = [],
links = [];
var vis = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
vis.append("rect")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.linkDistance(newDistance)
.nodes(nodes)
.links(links)
.gravity(.01)
.size([width, height]);
force.on("tick", function() {
vis.selectAll("line.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; });
vis.selectAll(".node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
vis.selectAll(".node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ") scale(0.30)"; });
});
var tempX = window.innerWidth/2;
var tempY = window.innerHeight/2;
var point = [tempX,tempY],
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/48799_806120304_700633127_n.jpg", x: tempX, y:tempY};
n = nodes.push(node);
vis.on("mousedown", function() {
var point = d3.mouse(this),
node = {imgsrc: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash4/211698_100002756979859_374256176_n.jpg", x:point[0], y:point[1]},
n = nodes.push(node);
nodesCreated++;
console.log(nodesCreated);
var tempCounter = 0;
newDistance == 10;
force.linkDistance(newDistance);
nodes.forEach(function(target) {
if (/*Math.sqrt(x * x + y * y) < 100 ||*/ tempCounter == 0) {
links.push({source: node, target: target});
tempCounter++;
}
});
restart();
});
function restart() {
force.start();
vis.selectAll("line.link")
.data(links)
.enter().insert("line", ".node")
.attr("class", "link");
var realNode = vis.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
realNode.append("image")
.attr("xlink:href", function(d) { return d.imgsrc; })
.attr("x", -8)
.attr("y", -8)
.attr("width", 160)
.attr("height", 160);
}​

Categories