How to add an extra level in a drill down pie chart? - d3.js

I have a nicely working drill down chart using d3.js, but I would like to add one (or more) deeper levels.
I can see how there's a level 0 (starting state) and a level 1 (one click deeper), but I can't seem to figure out how to implement that to a level 2 state (two clicks down).
Can somebody show me how to do that? I've got this so far: https://jsfiddle.net/yLsg841u/5/
<script type="text/javascript">
var salesData;
var chartInnerDiv = '<div class="innerCont" style="overflow: auto;top:100px; left: 400px; height:91% ; Width:100% ;position: relative;overflow: hidden;"/>';
var truncLengh = 30;
$(document).ready(function () {
Plot();
});
function Plot() {
TransformChartData(chartData, chartOptions, 0);
BuildPie("chart", chartData, chartOptions, 0)
}
function BuildPie(id, chartData, options, level) {
var xVarName;
var divisionRatio = 2.5;
var legendoffset = (level == 0) ? 0 : -40;
d3.selectAll("#" + id + " .innerCont").remove();
$("#" + id).append(chartInnerDiv);
chart = d3.select("#" + id + " .innerCont");
var yVarName = options[0].yaxis;
width = $(chart[0]).outerWidth(),
height = $(chart[0]).outerHeight(),
radius = Math.min(width, height) / divisionRatio;
if (level == 1) {
xVarName = options[0].xaxisl1;
}
else {
xVarName = options[0].xaxis;
}
var rcolor = d3.scale.ordinal().range(runningColors);
arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 200);
var arcOver = d3.svg.arc().outerRadius(radius + 20).innerRadius(radius - 180);
chart = chart
.append("svg") //append svg element inside #chart
.attr("width", width) //set width
.attr("height", height) //set height
.append("g")
.attr("transform", "translate(" + (width / divisionRatio) + "," + ((height / divisionRatio) + 30) + ")");
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.Total;
});
var g = chart.selectAll(".arc")
.data(pie(runningData))
.enter().append("g")
.attr("class", "arc");
var count = 0;
var path = g.append("path")
.attr("d", arc)
.attr("id", function (d) { return "arc-" + (count++); })
.style("opacity", function (d) {
return d.data["op"];
});
path.on("mouseenter", function (d) {
d3.select(this)
.attr("stroke", "white")
.transition()
.duration(200)
.attr("d", arcOver)
.attr("stroke-width", 1);
})
.on("mouseleave", function (d) {
d3.select(this).transition()
.duration(200)
.attr("d", arc)
.attr("stroke", "none");
})
.on("click", function (d) {
if (this._listenToEvents) {
// Reset inmediatelly
d3.select(this).attr("transform", "translate(0,0)")
// Change level on click if no transition has started
path.each(function () {
this._listenToEvents = false;
});
}
d3.selectAll("#" + id + " svg").remove();
if (level == 1) {
TransformChartData(chartData, options, 0, d.data[xVarName]);
BuildPie(id, chartData, options, 0);
}
else {
var nonSortedChart = chartData.sort(function (a, b) {
return parseFloat(b[options[0].yaxis]) - parseFloat(a[options[0].yaxis]);
});
TransformChartData(nonSortedChart, options, 1, d.data[xVarName]);
BuildPie(id, nonSortedChart, options, 1);
}
});
path.append("svg:title")
.text(function (d) {
return d.data["title"] + " (" + d.data[yVarName] + ")";
});
path.style("fill", function (d) {
return rcolor(d.data[xVarName]);
})
.transition().duration(1000).attrTween("d", tweenIn).each("end", function () {
this._listenToEvents = true;
});
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("opacity", 1)
.text(function (d) {
return d.data[yVarName];
});
count = 0;
var legend = chart.selectAll(".legend")
.data(runningData).enter()
.append("g").attr("class", "legend")
.attr("legend-id", function (d) {
return count++;
})
.attr("transform", function (d, i) {
return "translate(15," + (parseInt("-" + (runningData.length * 10)) + i * 28 + legendoffset) + ")";
})
.style("cursor", "pointer")
.on("click", function () {
var oarc = d3.select("#" + id + " #arc-" + $(this).attr("legend-id"));
oarc.style("opacity", 0.3)
.attr("stroke", "white")
.transition()
.duration(200)
.attr("d", arcOver)
.attr("stroke-width", 1);
setTimeout(function () {
oarc.style("opacity", function (d) {
return d.data["op"];
})
.attr("d", arc)
.transition()
.duration(200)
.attr("stroke", "none");
}, 1000);
});
var leg = legend.append("rect");
leg.attr("x", width / 2)
.attr("width", 18).attr("height", 18)
.style("fill", function (d) {
return rcolor(d[yVarName]);
})
.style("opacity", function (d) {
return d["op"];
});
legend.append("text").attr("x", (width / 2) - 5)
.attr("y", 9).attr("dy", ".35em")
.style("text-anchor", "end").text(function (d) {
return d.caption;
});
leg.append("svg:title")
.text(function (d) {
return d["title"] + " (" + d[yVarName] + ")";
});
function tweenOut(data) {
data.startAngle = data.endAngle = (2 * Math.PI);
var interpolation = d3.interpolate(this._current, data);
this._current = interpolation(0);
return function (t) {
return arc(interpolation(t));
};
}
function tweenIn(data) {
var interpolation = d3.interpolate({ startAngle: 0, endAngle: 0 }, data);
this._current = interpolation(0);
return function (t) {
return arc(interpolation(t));
};
}
}
function TransformChartData(chartData, opts, level, filter) {
var result = [];
var resultColors = [];
var counter = 0;
var hasMatch;
var xVarName;
var yVarName = opts[0].yaxis;
if (level == 1) {
xVarName = opts[0].xaxisl1;
for (var i in chartData) {
hasMatch = false;
for (var index = 0; index < result.length; ++index) {
var data = result[index];
if ((data[xVarName] == chartData[i][xVarName]) && (chartData[i][opts[0].xaxis]) == filter) {
result[index][yVarName] = result[index][yVarName] + chartData[i][yVarName];
hasMatch = true;
break;
}
}
if ((hasMatch == false) && ((chartData[i][opts[0].xaxis]) == filter)) {
if (result.length < 9) {
ditem = {}
ditem[xVarName] = chartData[i][xVarName];
ditem[yVarName] = chartData[i][yVarName];
ditem["caption"] = chartData[i][xVarName].substring(0, 10) + '...';
ditem["title"] = chartData[i][xVarName];
ditem["op"] = 1.0 - parseFloat("0." + (result.length));
result.push(ditem);
resultColors[counter] = opts[0].color[0][chartData[i][opts[0].xaxis]];
counter += 1;
}
}
}
}
else {
xVarName = opts[0].xaxis;
for (var i in chartData) {
hasMatch = false;
for (var index = 0; index < result.length; ++index) {
var data = result[index];
if (data[xVarName] == chartData[i][xVarName]) {
result[index][yVarName] = result[index][yVarName] + chartData[i][yVarName];
hasMatch = true;
break;
}
}
if (hasMatch == false) {
ditem = {};
ditem[xVarName] = chartData[i][xVarName];
ditem[yVarName] = chartData[i][yVarName];
ditem["caption"] = opts[0].captions != undefined ? opts[0].captions[0][chartData[i][xVarName]] : "";
ditem["title"] = opts[0].captions != undefined ? opts[0].captions[0][chartData[i][xVarName]] : "";
ditem["op"] = 1;
result.push(ditem);
resultColors[counter] = opts[0].color != undefined ? opts[0].color[0][chartData[i][xVarName]] : "";
counter += 1;
}
}
}
runningData = result;
runningColors = resultColors;
return;
}
chartOptions = [{
"captions": [{ "INDIA": "INDIA", "CANADA": "CANADA", "USA": "USA" }],
"color": [{ "INDIA": "#FFA500", "CANADA": "#0070C0", "USA": "#ff0000" }],
"xaxis": "Country",
"xaxisl1": "Model",
"yaxis": "Total"
}]
var chartData = [
{
"Country": "USA",
"Model": "Model 1",
"Total": 487
},
{
"Country": "USA",
"Model": "Model 2",
"Total": 185
},
{
"Country": "USA",
"Model": "Model 3",
"Total": 140
},
{
"Country": "USA",
"Model": "Model 4",
"Total": 108
},
{
"Country": "USA",
"Model": "Model 5",
"Total": 26
},
{
"Country": "USA",
"Model": "Model 6",
"Total": 106
},
{
"Country": "USA",
"Model": "Model 7",
"Total": 27
},
{
"Country": "USA",
"Model": "Model 8",
"Total": 44
},
{
"Country": "USA",
"Model": "Model 9",
"Total": 96
},
{
"Country": "INDIA",
"Model": "Model 1",
"Total": 411
},
{
"Country": "INDIA",
"Model": "Model 2",
"Total": 33
},
{
"Country": "INDIA",
"Model": "Model 3",
"Total": 32
},
{
"Country": "INDIA",
"Model": "Model 4",
"Total": 29
},
{
"Country": "INDIA",
"Model": "Model 5",
"Total": 29
},
{
"Country": "CANADA",
"Model": "Model 1",
"Total": 7
},
{
"Country": "CANADA",
"Model": "Model 2",
"Total": 20
},
{
"Country": "CANADA",
"Model": "Model 3",
"Total": 232
},
{
"Country": "CANADA",
"Model": "Model 4",
"Total": 117
}
];

I have tried adding one level.
Firstly, make sure you have json data for other level
Secondly, mention the 2nd level name in chartOptions as "xaxisl2":"YourLevelName"
Finally, add else if statement for (level==2) as follows:
TransformChartData(nonSortedChart, options, 2, d.data[xVarName]);
BuildPie(id, nonSortedChart, options, 2);
where xVarName=opts[0].xaxisl2;

Related

d3js force directed graph arrows not showing up

I have a force directed graph using version 5 of d3.js and would like to include arrow heads for each link. I've included the code below and posted a jsfiddle. I'm seeking guidance on why the arrow heads (id=end-arrow) are not showing up in the graph. I'm referencing the end-arrow as an attribute in the declaration of link: .attr("marker-end","url(#end-arrow)"), and I don't know how to troubleshoot this.
HTML:
<svg id="viz"></svg>
Javascript with d3.js version 5:
// based on https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868
var width = 600;
var height = 400;
var border = 1;
var bordercolor="black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes
var graph = {
"nodes": [
{"id": "4718871", "group": 2, "img": "https://derivationmap.net/static/multiplybothsidesby.png", "width": 298, "height": 30, "linear index": 2},
{"id": "2131616531", "group": 0, "img": "https://derivationmap.net/static/2131616531.png", "width": 103, "height": 30, "linear index": 0},
{"id": "9565166889", "group": 0, "img": "https://derivationmap.net/static/9565166889.png", "width": 24, "height": 23, "linear index": 0},
{"id": "9040079362", "group": 0, "img": "https://derivationmap.net/static/9040079362.png", "width": 18, "height": 30, "linear index": 0},
{"id": "9278347", "group": 1, "img": "https://derivationmap.net/static/declareinitialexpr.png", "width": 270, "height": 30, "linear index": 1},
{"id": "6286448", "group": 4, "img": "https://derivationmap.net/static/declarefinalexpr.png", "width": 255, "height": 30, "linear index": 4},
{"id": "2113211456", "group": 0, "img": "https://derivationmap.net/static/2113211456.png", "width": 121, "height": 34, "linear index": 0},
{"id": "2169431", "group": 3, "img": "https://derivationmap.net/static/dividebothsidesby.png", "width": 260, "height": 30, "linear index": 3},
{"id": "3131111133", "group": 0, "img": "https://derivationmap.net/static/3131111133.png", "width": 121, "height": 34, "linear index": 0}
],
"links": [
{"source": "2169431", "target": "2113211456", "value": 1},
{"source": "2113211456", "target": "6286448", "value": 1},
{"source": "9278347", "target": "3131111133", "value": 1},
{"source": "4718871", "target": "2131616531", "value": 1},
{"source": "9040079362", "target": "4718871", "value": 1},
{"source": "2131616531", "target": "2169431", "value": 1},
{"source": "3131111133", "target": "4718871", "value": 1},
{"source": "9565166889", "target": "2169431", "value": 1}
]
};
var label = {
"nodes": [],
"links": []
};
graph.nodes.forEach(function(d, i) {
label.nodes.push({node: d});
label.nodes.push({node: d});
label.links.push({
source: i * 2,
target: i * 2 + 1
});
});
var labelLayout = d3.forceSimulation(label.nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(label.links).distance(0).strength(2));
var graphLayout = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-3000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(1))
.force("y", d3.forceY(height / 2).strength(1))
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
.on("tick", ticked);
var adjlist = [];
graph.links.forEach(function(d) {
adjlist[d.source.index + "-" + d.target.index] = true;
adjlist[d.target.index + "-" + d.source.index] = true;
});
function neigh(a, b) {
return a == b || adjlist[a + "-" + b];
}
var svg = d3.select("#viz").attr("width", width).attr("height", height);
// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
.attr("id", "end-arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 6)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("svg:line")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "black");
// http://bl.ocks.org/AndrewStaroscik/5222370
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", height)
.attr("width", width)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
var container = svg.append("g");
svg.call(
d3.zoom()
.scaleExtent([.1, 4])
.on("zoom", function() { container.attr("transform", d3.event.transform); })
);
var link = container.append("g").attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke", "#aaa")
.attr("stroke-width", "1px")
.attr("marker-end","url(#end-arrow)");
var node = container.append("g").attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
node.on("mouseover", focus).on("mouseout", unfocus);
node.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var labelNode = container.append("g").attr("class", "labelNodes")
.selectAll("text")
.data(label.nodes)
.enter()
.append("image")
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
// I have no idea why the i%2 is needed; without it I get two images per node
// switching between i%2==1 and i%2==0 produces different image locations (?)
.attr("xlink:href", function(d, i) { return i % 2 == 1 ? "" : d.node.img; } )
.attr("x", 0)
.attr("y", 0)
// the following alter the image size
.attr("width", function(d, i) { return d.node.width/2; })
.attr("height", function(d, i) { return d.node.height/2; })
// .append("text")
// .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
// .style("fill", "#555")
// .style("font-family", "Arial")
// .style("font-size", 12)
.style("pointer-events", "none"); // to prevent mouseover/drag capture
node.on("mouseover", focus).on("mouseout", unfocus);
function ticked() {
node.call(updateNode);
link.call(updateLink);
labelLayout.alphaTarget(0.3).restart();
labelNode.each(function(d, i) {
if(i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 16;
this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
labelNode.call(updateNode);
}
function fixna(x) {
if (isFinite(x)) return x;
return 0;
}
function focus(d) {
var index = d3.select(d3.event.target).datum().index;
node.style("opacity", function(o) {
return neigh(index, o.index) ? 1 : 0.1;
});
labelNode.attr("display", function(o) {
return neigh(index, o.node.index) ? "block": "none";
});
link.style("opacity", function(o) {
return o.source.index == index || o.target.index == index ? 1 : 0.1;
});
}
function unfocus() {
labelNode.attr("display", "block");
node.style("opacity", 1);
link.style("opacity", 1);
}
function updateLink(link) {
link.attr("x1", function(d) { return fixna(d.source.x); })
.attr("y1", function(d) { return fixna(d.source.y); })
.attr("x2", function(d) { return fixna(d.target.x); })
.attr("y2", function(d) { return fixna(d.target.y); });
}
function updateNode(node) {
node.attr("transform", function(d) {
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
});
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
if (!d3.event.active) graphLayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) graphLayout.alphaTarget(0);
d.fx = null;
d.fy = null;
}
Based on feedback in the d3js Slack channel, there were two issues:
In the definition of the arrow, needed .append("svg:path")
With that fixed, the arrows were too small and were hidden behind the node circles. By making the arrows larger, they were visible.
I've updated http://bl.ocks.org/bhpayne/0a8ef2ae6d79aa185dcf2c3a385daf25 and the revised code is below:
HTML
<svg id="viz"></svg>
Javascript + d3js
// based on https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868
var width = 600;
var height = 400;
var border = 3;
var bordercolor = "black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes
var graph = {
"nodes": [{
"id": "4718871",
"group": 2,
"img": "https://derivationmap.net/static/multiplybothsidesby.png",
"width": 298,
"height": 30,
"linear index": 2
},
{
"id": "2131616531",
"group": 0,
"img": "https://derivationmap.net/static/2131616531.png",
"width": 103,
"height": 30,
"linear index": 0
},
{
"id": "9565166889",
"group": 0,
"img": "https://derivationmap.net/static/9565166889.png",
"width": 24,
"height": 23,
"linear index": 0
},
{
"id": "9040079362",
"group": 0,
"img": "https://derivationmap.net/static/9040079362.png",
"width": 18,
"height": 30,
"linear index": 0
},
{
"id": "9278347",
"group": 1,
"img": "https://derivationmap.net/static/declareinitialexpr.png",
"width": 270,
"height": 30,
"linear index": 1
},
{
"id": "6286448",
"group": 4,
"img": "https://derivationmap.net/static/declarefinalexpr.png",
"width": 255,
"height": 30,
"linear index": 4
},
{
"id": "2113211456",
"group": 0,
"img": "https://derivationmap.net/static/2113211456.png",
"width": 121,
"height": 34,
"linear index": 0
},
{
"id": "2169431",
"group": 3,
"img": "https://derivationmap.net/static/dividebothsidesby.png",
"width": 260,
"height": 30,
"linear index": 3
},
{
"id": "3131111133",
"group": 0,
"img": "https://derivationmap.net/static/3131111133.png",
"width": 121,
"height": 34,
"linear index": 0
}
],
"links": [{
"source": "2169431",
"target": "2113211456",
"value": 1
},
{
"source": "2113211456",
"target": "6286448",
"value": 1
},
{
"source": "9278347",
"target": "3131111133",
"value": 1
},
{
"source": "4718871",
"target": "2131616531",
"value": 1
},
{
"source": "9040079362",
"target": "4718871",
"value": 1
},
{
"source": "2131616531",
"target": "2169431",
"value": 1
},
{
"source": "3131111133",
"target": "4718871",
"value": 1
},
{
"source": "9565166889",
"target": "2169431",
"value": 1
}
]
};
var label = {
"nodes": [],
"links": []
};
graph.nodes.forEach(function(d, i) {
label.nodes.push({
node: d
});
label.nodes.push({
node: d
});
label.links.push({
source: i * 2,
target: i * 2 + 1
});
});
var labelLayout = d3.forceSimulation(label.nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(label.links).distance(0).strength(2));
var graphLayout = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-3000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(1))
.force("y", d3.forceY(height / 2).strength(1))
.force("link", d3.forceLink(graph.links).id(function(d) {
return d.id;
}).distance(50).strength(1))
.on("tick", ticked);
var adjlist = [];
graph.links.forEach(function(d) {
adjlist[d.source.index + "-" + d.target.index] = true;
adjlist[d.target.index + "-" + d.source.index] = true;
});
function neigh(a, b) {
return a == b || adjlist[a + "-" + b];
}
var svg = d3.select("#viz").attr("width", width).attr("height", height);
// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
.attr("id", "end-arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("markerWidth", 20)
.attr("markerHeight", 20)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L20,0L0,5")
.attr("fill", "#000");
// http://bl.ocks.org/AndrewStaroscik/5222370
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", height)
.attr("width", width)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
var container = svg.append("g");
svg.call(
d3.zoom()
.scaleExtent([.1, 4])
.on("zoom", function() {
container.attr("transform", d3.event.transform);
})
);
var link = container.append("g").attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke", "#aaa")
.attr("marker-end", "url(#end-arrow)")
.attr("stroke-width", "1px");
var node = container.append("g").attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", function(d) {
return color(d.group);
})
node.on("mouseover", focus).on("mouseout", unfocus);
node.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var labelNode = container.append("g").attr("class", "labelNodes")
.selectAll("text")
.data(label.nodes)
.enter()
.append("image")
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
// I have no idea why the i%2 is needed; without it I get two images per node
// switching between i%2==1 and i%2==0 produces different image locations (?)
.attr("xlink:href", function(d, i) {
return i % 2 == 1 ? "" : d.node.img;
})
.attr("x", 0)
.attr("y", 0)
// the following alter the image size
.attr("width", function(d, i) {
return d.node.width / 2;
})
.attr("height", function(d, i) {
return d.node.height / 2;
})
// .append("text")
// .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
// .style("fill", "#555")
// .style("font-family", "Arial")
// .style("font-size", 12)
.style("pointer-events", "none"); // to prevent mouseover/drag capture
node.on("mouseover", focus).on("mouseout", unfocus);
function ticked() {
node.call(updateNode);
link.call(updateLink);
labelLayout.alphaTarget(0.3).restart();
labelNode.each(function(d, i) {
if (i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 16;
this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
labelNode.call(updateNode);
}
function fixna(x) {
if (isFinite(x)) return x;
return 0;
}
function focus(d) {
var index = d3.select(d3.event.target).datum().index;
node.style("opacity", function(o) {
return neigh(index, o.index) ? 1 : 0.1;
});
labelNode.attr("display", function(o) {
return neigh(index, o.node.index) ? "block" : "none";
});
link.style("opacity", function(o) {
return o.source.index == index || o.target.index == index ? 1 : 0.1;
});
}
function unfocus() {
labelNode.attr("display", "block");
node.style("opacity", 1);
link.style("opacity", 1);
}
function updateLink(link) {
link.attr("x1", function(d) {
return fixna(d.source.x);
})
.attr("y1", function(d) {
return fixna(d.source.y);
})
.attr("x2", function(d) {
return fixna(d.target.x);
})
.attr("y2", function(d) {
return fixna(d.target.y);
});
}
function updateNode(node) {
node.attr("transform", function(d) {
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
});
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
if (!d3.event.active) graphLayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) graphLayout.alphaTarget(0);
d.fx = null;
d.fy = null;
}

d3js force node xy start position

I have a node with fx/fy parameter on simulation starting. This node is fixed on the correct position. But now I want to define x and y coordinates for one or multiple other nodes (in this example: number 10 in jsfiddle), the purpose is to start the simulation of this "unfixed" nodes on a given position and not 0/0. Why is the node fixed on 0/0?
jsfiddle example: https://jsfiddle.net/6g9howo7/2/
var nodes = [
{
"id" : "1",
"fx" : "225",
"fy" : "225"
},
{
"id" : "2"
},
{
"id" : "3"
},
{
"id" : "4"
},
{
"id" : "5"
},
{
"id" : "6"
},
{
"id" : "7"
},
{
"id" : "8"
},
{
"id" : "9"
},
{
"id" : "10",
"x" : "125",
"y" : "125"
},
{
"id" : "11"
},
{
"id" : "12"
},
{
"id" : "13"
},
{
"id" : "14"
},
{
"id" : "15"
}
]
var links =
[
{
"source" : 1,
"target" : 2
},
{
"source" : 1,
"target" : 3
},
{
"source" : 1,
"target" : 4
},
{
"source" : 1,
"target" : 5
},
{
"source" : 1,
"target" : 6
},
{
"source" : 1,
"target" : 7
},
{
"source" : 1,
"target" : 8
},
{
"source" : 1,
"target" : 9
},
{
"source" : 1,
"target" : 10
},
{
"source" : 10,
"target" : 11
},
{
"source" : 10,
"target" : 12
},
{
"source" : 10,
"target" : 13
},
{
"source" : 10,
"target" : 14
},
{
"source" : 10,
"target" : 15
}
]
var svg = d3.select("svg")
var zoom = d3.zoom()
.on("zoom", zoomed);
//.scaleExtent([1 / 8, 4])
svg
.call(zoom).on("dblclick.zoom", null)
var g = svg.append("g");
function zoomed() {
g.attr("transform", d3.event.transform);
}
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
//.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(function(d) {return d.distance/2;}).strength(1))
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(10).strength(1))
.force("charge", d3.forceManyBody().strength(-10).distanceMax(100));
//.force("center", d3.forceCenter(1000, 1000));
//.force("y", d3.forceY(500))
//.force("x", d3.forceX(500));
//.force("collide",d3.forceCollide(.5));
//.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(4) );
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1 /*function(d) { return Math.sqrt(2); }*/)
.style("stroke", 'red');
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r",3)
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; })
.on("dblclick", dblclick)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
simulation.nodes(nodes)
// .alphaDecay(0.5)
.velocityDecay(0.1)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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; });
}
function dblclick(d) {
d.fx = null;
d.fy = null;
}
function dragstarted(d) {
//if (!d3.event.active) simulation.alphaTarget(0.3).restart();
simulation.restart();
// simulation.alpha -> redémarre la période de simulation
simulation.alpha(1.0);
d.fx = d.x;
d.fy = d.y;
}
//Grid
var grid = 50;
function dragged(d,i) {
//force.stop();
//var grid = 50;
var gx = Math.round(d3.event.x/grid)*grid;
var gy = Math.round(d3.event.y/grid)*grid;
d.fx = gx;
d.fy = gy;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// console.log(d);
// d.fx = null;
// d.fy = null;
// d.fixed = true;
}
//Grid
var width = 7000;
var height = 7000;
var lineGraph = g.append("g")
.attr("width", width)
.attr("height", height);
// Using for loop to draw multiple horizontal lines
for (var j=grid; j <= width-grid; j=j+grid) {
lineGraph.append("svg:line")
.attr("x1", grid)
.attr("y1", j)
.attr("x2", width-grid)
.attr("y2", j)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
// Using for loop to draw multiple vertical lines
for (var j=grid; j <= height-grid; j=j+grid) {
lineGraph.append("svg:line")
.attr("x1", j)
.attr("y1", grid)
.attr("x2", j)
.attr("y2", height-grid)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
Your approach is correct, setting x and y defines the start position. However, they have to be numbers, not strings.
Therefore, instead of:
{
"id": "10",
"x": "125",
"y": "125"
}
It should be:
{
"id": "10",
"x": 125,
"y": 125
}
Here is your code with that change:
var nodes = [{
"id": "1",
"fx": "225",
"fy": "225"
}, {
"id": "2"
}, {
"id": "3"
}, {
"id": "4"
}, {
"id": "5"
}, {
"id": "6"
}, {
"id": "7"
}, {
"id": "8"
}, {
"id": "9"
}, {
"id": "10",
"x": 125,
"y": 125
}, {
"id": "11"
}, {
"id": "12"
}, {
"id": "13"
}, {
"id": "14"
}, {
"id": "15"
}]
var links = [{
"source": 1,
"target": 2
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 1,
"target": 8
}, {
"source": 1,
"target": 9
}, {
"source": 1,
"target": 10
}, {
"source": 10,
"target": 11
}, {
"source": 10,
"target": 12
}, {
"source": 10,
"target": 13
}, {
"source": 10,
"target": 14
}, {
"source": 10,
"target": 15
}]
var svg = d3.select("svg")
var zoom = d3.zoom()
.on("zoom", zoomed);
//.scaleExtent([1 / 8, 4])
svg
.call(zoom).on("dblclick.zoom", null)
var g = svg.append("g");
function zoomed() {
g.attr("transform", d3.event.transform);
}
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
//.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(function(d) {return d.distance/2;}).strength(1))
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(10).strength(1))
.force("charge", d3.forceManyBody().strength(-10).distanceMax(100));
//.force("center", d3.forceCenter(1000, 1000));
//.force("y", d3.forceY(500))
//.force("x", d3.forceX(500));
//.force("collide",d3.forceCollide(.5));
//.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(4) );
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1 /*function(d) { return Math.sqrt(2); }*/ )
.style("stroke", 'red');
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 3)
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; })
.on("dblclick", dblclick)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) {
return d.id;
});
simulation.nodes(nodes)
// .alphaDecay(0.5)
.velocityDecay(0.1)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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;
});
}
function dblclick(d) {
d.fx = null;
d.fy = null;
}
function dragstarted(d) {
//if (!d3.event.active) simulation.alphaTarget(0.3).restart();
simulation.restart();
// simulation.alpha -> redémarre la période de simulation
simulation.alpha(1.0);
d.fx = d.x;
d.fy = d.y;
}
//Grid
var grid = 50;
function dragged(d, i) {
//force.stop();
//var grid = 50;
var gx = Math.round(d3.event.x / grid) * grid;
var gy = Math.round(d3.event.y / grid) * grid;
d.fx = gx;
d.fy = gy;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// console.log(d);
// d.fx = null;
// d.fy = null;
// d.fixed = true;
}
//Grid
var width = 7000;
var height = 7000;
var lineGraph = g.append("g")
.attr("width", width)
.attr("height", height);
// Using for loop to draw multiple horizontal lines
for (var j = grid; j <= width - grid; j = j + grid) {
lineGraph.append("svg:line")
.attr("x1", grid)
.attr("y1", j)
.attr("x2", width - grid)
.attr("y2", j)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
// Using for loop to draw multiple vertical lines
for (var j = grid; j <= height - grid; j = j + grid) {
lineGraph.append("svg:line")
.attr("x1", j)
.attr("y1", grid)
.attr("x2", j)
.attr("y2", height - grid)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: flex;
font-family: sans-serif;
font-size: 75%;
}
/* SVG styles */
svg {
flex-basis: 100%;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

d3 update path with maximum value of area

I display a d3.area(). To show the highest value, I draw a horizontal line with d3.line() at the maximum value of the dataset. This value is calculated using:
var max_out = d3.max(data, function(d){ return +d.out; });
To brush through the graph, I use the setup as in this example:
As soon as the area is brushed, I would also like to move the "maximum line" up and down reflecting the new domain instead of using the whole dataset.
Any pointers?
Let's have a line function definition, in the first time only defining the x attribute:
var line = d3.line()
.x(function(d) { return x(d.date); });
Right after you append your area in the main view, add another path, which will be used to draw your horizontal line above the corresponding area:
focus.append("path")
.attr("class", "line");
Notice that I'm giving it a line class for styling.
We're also gonna draw the line, at first simply using the function you are currently using to determine the peak of your data:
.attr("d", line.y(function () {
return y(max);
}));
... and feed our line generator the right data :):
.datum(data)
To summarize, here's what we got on initialization:
// There's your main view's data area
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
// There's your 'peak' horizontal line
var max = d3.max(data, function(d){ return d.price; });
focus.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line.y(function () {
return y(max);
}));
In the functions that trigger a re-drawing of your elements, you simply have to update the d attribute of your path.line element.
We're gonna simply do the following:
Filter out data points that aren't within the updated domain of your x-axis
Evaluate the maximum value of that sub-set (using the method you described)
Let D3 generate the new value for the d attribute and set it.
That translates into the following:
var lo = x.domain()[0],
hi = x.domain()[1];
var max = d3.max(data.filter(function (d) {
return d.date.getTime() >= lo && d.date.getTime() <= hi;
}), function(d){ return d.price; });
focus.select(".line").attr("d", line.y(function () {
return y(max);
}));
See an example in action in the snippet below or on JSFiddle.
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 110, left: 40},
margin2 = {top: 180, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
height2 = +svg.attr("height") - margin2.top - margin2.bottom;
var x = d3.scaleTime().range([0, width]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.price); });
var area2 = d3.area()
.x(function(d) { return x2(d.date); })
.y0(height2)
.y1(function(d) { return y2(d.price); });
var line = d3.line()
.x(function(d) { return x(d.date); });
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var data;
(function (_data) {
data = _data;
data.forEach(function (d) {
d.date = new Date(d.date);
});
data = (function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
});
})(data, function (d) {return d.date;});
data.sort(function(a,b) {
return new Date(a.date).getTime() - new Date(b.date).getTime();
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.price; })]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
var max = d3.max(data, function(d){ return d.price; });
focus.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line.y(function () {
return y(max);
}));
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2);
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
})([{"date":"1999-12-31T23:00:00.000Z","price":1394.46},{"date":"2000-01-31T23:00:00.000Z","price":1366.42},{"date":"2000-02-29T23:00:00.000Z","price":1498.58},{"date":"2000-03-31T22:00:00.000Z","price":1452.43},{"date":"2000-04-30T22:00:00.000Z","price":1420.6},{"date":"2000-05-31T22:00:00.000Z","price":1454.6},{"date":"2000-06-30T22:00:00.000Z","price":1430.83},{"date":"2000-07-31T22:00:00.000Z","price":1517.68},{"date":"2000-08-31T22:00:00.000Z","price":1436.51},{"date":"2000-09-30T22:00:00.000Z","price":1429.4},{"date":"2000-10-31T23:00:00.000Z","price":1314.95},{"date":"2000-11-30T23:00:00.000Z","price":1320.28},{"date":"2000-12-31T23:00:00.000Z","price":1366.01},{"date":"2001-01-31T23:00:00.000Z","price":1239.94},{"date":"2001-02-28T23:00:00.000Z","price":1160.33},{"date":"2001-03-31T22:00:00.000Z","price":1249.46},{"date":"2001-04-30T22:00:00.000Z","price":1255.82},{"date":"2001-05-31T22:00:00.000Z","price":1224.38},{"date":"2001-06-30T22:00:00.000Z","price":1211.23},{"date":"2001-07-31T22:00:00.000Z","price":1133.58},{"date":"2001-08-31T22:00:00.000Z","price":1040.94},{"date":"2001-09-30T22:00:00.000Z","price":1059.78},{"date":"2001-10-31T23:00:00.000Z","price":1139.45},{"date":"2001-11-30T23:00:00.000Z","price":1148.08},{"date":"2001-12-31T23:00:00.000Z","price":1130.2},{"date":"2002-01-31T23:00:00.000Z","price":1106.73},{"date":"2002-02-28T23:00:00.000Z","price":1147.39},{"date":"2002-03-31T22:00:00.000Z","price":1076.92},{"date":"2002-04-30T22:00:00.000Z","price":1067.14},{"date":"2002-05-31T22:00:00.000Z","price":989.82},{"date":"2002-06-30T22:00:00.000Z","price":911.62},{"date":"2002-07-31T22:00:00.000Z","price":916.07},{"date":"2002-08-31T22:00:00.000Z","price":815.28},{"date":"2002-09-30T22:00:00.000Z","price":885.76},{"date":"2002-10-31T23:00:00.000Z","price":936.31},{"date":"2002-11-30T23:00:00.000Z","price":879.82},{"date":"2002-12-31T23:00:00.000Z","price":855.7},{"date":"2003-01-31T23:00:00.000Z","price":841.15},{"date":"2003-02-28T23:00:00.000Z","price":848.18},{"date":"2003-03-31T22:00:00.000Z","price":916.92},{"date":"2003-04-30T22:00:00.000Z","price":963.59},{"date":"2003-05-31T22:00:00.000Z","price":974.5},{"date":"2003-06-30T22:00:00.000Z","price":990.31},{"date":"2003-07-31T22:00:00.000Z","price":1008.01},{"date":"2003-08-31T22:00:00.000Z","price":995.97},{"date":"2003-09-30T22:00:00.000Z","price":1050.71},{"date":"2003-10-31T23:00:00.000Z","price":1058.2},{"date":"2003-11-30T23:00:00.000Z","price":1111.92},{"date":"2003-12-31T23:00:00.000Z","price":1131.13},{"date":"2004-01-31T23:00:00.000Z","price":1144.94},{"date":"2004-02-29T23:00:00.000Z","price":1126.21},{"date":"2004-03-31T22:00:00.000Z","price":1107.3},{"date":"2004-04-30T22:00:00.000Z","price":1120.68},{"date":"2004-05-31T22:00:00.000Z","price":1140.84},{"date":"2004-06-30T22:00:00.000Z","price":1101.72},{"date":"2004-07-31T22:00:00.000Z","price":1104.24},{"date":"2004-08-31T22:00:00.000Z","price":1114.58},{"date":"2004-09-30T22:00:00.000Z","price":1130.2},{"date":"2004-10-31T23:00:00.000Z","price":1173.82},{"date":"2004-11-30T23:00:00.000Z","price":1211.92},{"date":"2004-12-31T23:00:00.000Z","price":1181.27},{"date":"2005-01-31T23:00:00.000Z","price":1203.6},{"date":"2005-02-28T23:00:00.000Z","price":1180.59},{"date":"2005-03-31T22:00:00.000Z","price":1156.85},{"date":"2005-04-30T22:00:00.000Z","price":1191.5},{"date":"2005-05-31T22:00:00.000Z","price":1191.33},{"date":"2005-06-30T22:00:00.000Z","price":1234.18},{"date":"2005-07-31T22:00:00.000Z","price":1220.33},{"date":"2005-08-31T22:00:00.000Z","price":1228.81},{"date":"2005-09-30T22:00:00.000Z","price":1207.01},{"date":"2005-10-31T23:00:00.000Z","price":1249.48},{"date":"2005-11-30T23:00:00.000Z","price":1248.29},{"date":"2005-12-31T23:00:00.000Z","price":1280.08},{"date":"2006-01-31T23:00:00.000Z","price":1280.66},{"date":"2006-02-28T23:00:00.000Z","price":1294.87},{"date":"2006-03-31T22:00:00.000Z","price":1310.61},{"date":"2006-04-30T22:00:00.000Z","price":1270.09},{"date":"2006-05-31T22:00:00.000Z","price":1270.2},{"date":"2006-06-30T22:00:00.000Z","price":1276.66},{"date":"2006-07-31T22:00:00.000Z","price":1303.82},{"date":"2006-08-31T22:00:00.000Z","price":1335.85},{"date":"2006-09-30T22:00:00.000Z","price":1377.94},{"date":"2006-10-31T23:00:00.000Z","price":1400.63},{"date":"2006-11-30T23:00:00.000Z","price":1418.3},{"date":"2006-12-31T23:00:00.000Z","price":1438.24},{"date":"2007-01-31T23:00:00.000Z","price":1406.82},{"date":"2007-02-28T23:00:00.000Z","price":1420.86},{"date":"2007-03-31T22:00:00.000Z","price":1482.37},{"date":"2007-04-30T22:00:00.000Z","price":1530.62},{"date":"2007-05-31T22:00:00.000Z","price":1503.35},{"date":"2007-06-30T22:00:00.000Z","price":1455.27},{"date":"2007-07-31T22:00:00.000Z","price":1473.99},{"date":"2007-08-31T22:00:00.000Z","price":1526.75},{"date":"2007-09-30T22:00:00.000Z","price":1549.38},{"date":"2007-10-31T23:00:00.000Z","price":1481.14},{"date":"2007-11-30T23:00:00.000Z","price":1468.36},{"date":"2007-12-31T23:00:00.000Z","price":1378.55},{"date":"2008-01-31T23:00:00.000Z","price":1330.63},{"date":"2008-02-29T23:00:00.000Z","price":1322.7},{"date":"2008-03-31T22:00:00.000Z","price":1385.59},{"date":"2008-04-30T22:00:00.000Z","price":1400.38},{"date":"2008-05-31T22:00:00.000Z","price":1280},{"date":"2008-06-30T22:00:00.000Z","price":1267.38},{"date":"2008-07-31T22:00:00.000Z","price":1282.83},{"date":"2008-08-31T22:00:00.000Z","price":1166.36},{"date":"2008-09-30T22:00:00.000Z","price":968.75},{"date":"2008-10-31T23:00:00.000Z","price":896.24},{"date":"2008-11-30T23:00:00.000Z","price":903.25},{"date":"2008-12-31T23:00:00.000Z","price":825.88},{"date":"2009-01-31T23:00:00.000Z","price":735.09},{"date":"2009-02-28T23:00:00.000Z","price":797.87},{"date":"2009-03-31T22:00:00.000Z","price":872.81},{"date":"2009-04-30T22:00:00.000Z","price":919.14},{"date":"2009-05-31T22:00:00.000Z","price":919.32},{"date":"2009-06-30T22:00:00.000Z","price":987.48},{"date":"2009-07-31T22:00:00.000Z","price":1020.62},{"date":"2009-08-31T22:00:00.000Z","price":1057.08},{"date":"2009-09-30T22:00:00.000Z","price":1036.19},{"date":"2009-10-31T23:00:00.000Z","price":1095.63},{"date":"2009-11-30T23:00:00.000Z","price":1115.1},{"date":"2009-12-31T23:00:00.000Z","price":1073.87},{"date":"2010-01-31T23:00:00.000Z","price":1104.49},{"date":"2010-02-28T23:00:00.000Z","price":1140.45 }]);
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
var lo = x.domain()[0],
hi = x.domain()[1];
var max = d3.max(data.filter(function (d) {
return d.date.getTime() >= lo && d.date.getTime() <= hi;
}), function(d){ return d.price; });
focus.select(".line").attr("d", line.y(function () {
return y(max);
}));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
var lo = x.domain()[0],
hi = x.domain()[1];
var max = d3.max(data.filter(function (d) {
return d.date.getTime() >= lo && d.date.getTime() <= hi;
}), function(d){ return d.price; });
focus.select(".line").attr("d", line.y(function () {
return y(max);
}));
}
function type(d) {
d.date = parseDate(d.date);
d.price = +d.price;
return d;
}
.area {
fill: steelblue;
clip-path: url(#clip);
}
.line {
stroke: red;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="250"></svg>
Something like this:
focus.append("line")
.attr("class", "peak")
.style("stroke", "orange")
.attr("stroke-width", 3);
function setPeak(){
var maxY = {
x: null,
y: -1e100
};
data.forEach(function(d){
if (d.date >= x.domain()[0] &&
d.date <= x.domain()[1] &&
d.price > maxY.y){
maxY.y = d.price;
maxY.x = d.date;
}
});
d3.select(".peak")
.attr("x1", x(maxY.x))
.attr("x2", x(maxY.x))
.attr("y1", 0)
.attr("y2", height);
}
setPeak();
Running code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.area {
fill: steelblue;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<svg width="400" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 110,
left: 40
},
margin2 = {
top: 230,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
height2 = +svg.attr("height") - margin2.top - margin2.bottom;
var x = d3.scaleTime().range([0, width]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var area = d3.area()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x(d.date);
})
.y0(height)
.y1(function(d) {
return y(d.price);
});
var area2 = d3.area()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x2(d.date);
})
.y0(height2)
.y1(function(d) {
return y2(d.price);
});
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
//d3.csv("data.csv", type, function(error, data) {
// if (error) throw error;
var data = [{
"date": "1999-12-31T23:00:00.000Z",
"price": 1394.46
}, {
"date": "2000-01-31T23:00:00.000Z",
"price": 1366.42
}, {
"date": "2000-02-29T23:00:00.000Z",
"price": 1498.58
}, {
"date": "2000-03-31T22:00:00.000Z",
"price": 1452.43
}, {
"date": "2000-04-30T22:00:00.000Z",
"price": 1420.6
}, {
"date": "2000-05-31T22:00:00.000Z",
"price": 1454.6
}, {
"date": "2000-06-30T22:00:00.000Z",
"price": 1430.83
}, {
"date": "2000-07-31T22:00:00.000Z",
"price": 1517.68
}, {
"date": "2000-08-31T22:00:00.000Z",
"price": 1436.51
}, {
"date": "2000-09-30T22:00:00.000Z",
"price": 1429.4
}, {
"date": "2000-10-31T23:00:00.000Z",
"price": 1314.95
}, {
"date": "2000-11-30T23:00:00.000Z",
"price": 1320.28
}, {
"date": "2000-12-31T23:00:00.000Z",
"price": 1366.01
}, {
"date": "2001-01-31T23:00:00.000Z",
"price": 1239.94
}, {
"date": "2001-02-28T23:00:00.000Z",
"price": 1160.33
}, {
"date": "2001-03-31T22:00:00.000Z",
"price": 1249.46
}, {
"date": "2001-04-30T22:00:00.000Z",
"price": 1255.82
}, {
"date": "2001-05-31T22:00:00.000Z",
"price": 1224.38
}, {
"date": "2001-06-30T22:00:00.000Z",
"price": 1211.23
}, {
"date": "2001-07-31T22:00:00.000Z",
"price": 1133.58
}, {
"date": "2001-08-31T22:00:00.000Z",
"price": 1040.94
}, {
"date": "2001-09-30T22:00:00.000Z",
"price": 1059.78
}, {
"date": "2001-10-31T23:00:00.000Z",
"price": 1139.45
}, {
"date": "2001-11-30T23:00:00.000Z",
"price": 1148.08
}, {
"date": "2001-12-31T23:00:00.000Z",
"price": 1130.2
}, {
"date": "2002-01-31T23:00:00.000Z",
"price": 1106.73
}, {
"date": "2002-02-28T23:00:00.000Z",
"price": 1147.39
}, {
"date": "2002-03-31T22:00:00.000Z",
"price": 1076.92
}, {
"date": "2002-04-30T22:00:00.000Z",
"price": 1067.14
}, {
"date": "2002-05-31T22:00:00.000Z",
"price": 989.82
}, {
"date": "2002-06-30T22:00:00.000Z",
"price": 911.62
}, {
"date": "2002-07-31T22:00:00.000Z",
"price": 916.07
}, {
"date": "2002-08-31T22:00:00.000Z",
"price": 815.28
}, {
"date": "2002-09-30T22:00:00.000Z",
"price": 885.76
}, {
"date": "2002-10-31T23:00:00.000Z",
"price": 936.31
}, {
"date": "2002-11-30T23:00:00.000Z",
"price": 879.82
}, {
"date": "2002-12-31T23:00:00.000Z",
"price": 855.7
}, {
"date": "2003-01-31T23:00:00.000Z",
"price": 841.15
}, {
"date": "2003-02-28T23:00:00.000Z",
"price": 848.18
}, {
"date": "2003-03-31T22:00:00.000Z",
"price": 916.92
}, {
"date": "2003-04-30T22:00:00.000Z",
"price": 963.59
}, {
"date": "2003-05-31T22:00:00.000Z",
"price": 974.5
}, {
"date": "2003-06-30T22:00:00.000Z",
"price": 990.31
}, {
"date": "2003-07-31T22:00:00.000Z",
"price": 1008.01
}, {
"date": "2003-08-31T22:00:00.000Z",
"price": 995.97
}, {
"date": "2003-09-30T22:00:00.000Z",
"price": 1050.71
}, {
"date": "2003-10-31T23:00:00.000Z",
"price": 1058.2
}, {
"date": "2003-11-30T23:00:00.000Z",
"price": 1111.92
}, {
"date": "2003-12-31T23:00:00.000Z",
"price": 1131.13
}, {
"date": "2004-01-31T23:00:00.000Z",
"price": 1144.94
}, {
"date": "2004-02-29T23:00:00.000Z",
"price": 1126.21
}, {
"date": "2004-03-31T22:00:00.000Z",
"price": 1107.3
}, {
"date": "2004-04-30T22:00:00.000Z",
"price": 1120.68
}, {
"date": "2004-05-31T22:00:00.000Z",
"price": 1140.84
}, {
"date": "2004-06-30T22:00:00.000Z",
"price": 1101.72
}, {
"date": "2004-07-31T22:00:00.000Z",
"price": 1104.24
}, {
"date": "2004-08-31T22:00:00.000Z",
"price": 1114.58
}, {
"date": "2004-09-30T22:00:00.000Z",
"price": 1130.2
}, {
"date": "2004-10-31T23:00:00.000Z",
"price": 1173.82
}, {
"date": "2004-11-30T23:00:00.000Z",
"price": 1211.92
}, {
"date": "2004-12-31T23:00:00.000Z",
"price": 1181.27
}, {
"date": "2005-01-31T23:00:00.000Z",
"price": 1203.6
}, {
"date": "2005-02-28T23:00:00.000Z",
"price": 1180.59
}, {
"date": "2005-03-31T22:00:00.000Z",
"price": 1156.85
}, {
"date": "2005-04-30T22:00:00.000Z",
"price": 1191.5
}, {
"date": "2005-05-31T22:00:00.000Z",
"price": 1191.33
}, {
"date": "2005-06-30T22:00:00.000Z",
"price": 1234.18
}, {
"date": "2005-07-31T22:00:00.000Z",
"price": 1220.33
}, {
"date": "2005-08-31T22:00:00.000Z",
"price": 1228.81
}, {
"date": "2005-09-30T22:00:00.000Z",
"price": 1207.01
}, {
"date": "2005-10-31T23:00:00.000Z",
"price": 1249.48
}, {
"date": "2005-11-30T23:00:00.000Z",
"price": 1248.29
}, {
"date": "2005-12-31T23:00:00.000Z",
"price": 1280.08
}, {
"date": "2006-01-31T23:00:00.000Z",
"price": 1280.66
}, {
"date": "2006-02-28T23:00:00.000Z",
"price": 1294.87
}, {
"date": "2006-03-31T22:00:00.000Z",
"price": 1310.61
}, {
"date": "2006-04-30T22:00:00.000Z",
"price": 1270.09
}, {
"date": "2006-05-31T22:00:00.000Z",
"price": 1270.2
}, {
"date": "2006-06-30T22:00:00.000Z",
"price": 1276.66
}, {
"date": "2006-07-31T22:00:00.000Z",
"price": 1303.82
}, {
"date": "2006-08-31T22:00:00.000Z",
"price": 1335.85
}, {
"date": "2006-09-30T22:00:00.000Z",
"price": 1377.94
}, {
"date": "2006-10-31T23:00:00.000Z",
"price": 1400.63
}, {
"date": "2006-11-30T23:00:00.000Z",
"price": 1418.3
}, {
"date": "2006-12-31T23:00:00.000Z",
"price": 1438.24
}, {
"date": "2007-01-31T23:00:00.000Z",
"price": 1406.82
}, {
"date": "2007-02-28T23:00:00.000Z",
"price": 1420.86
}, {
"date": "2007-03-31T22:00:00.000Z",
"price": 1482.37
}, {
"date": "2007-04-30T22:00:00.000Z",
"price": 1530.62
}, {
"date": "2007-05-31T22:00:00.000Z",
"price": 1503.35
}, {
"date": "2007-06-30T22:00:00.000Z",
"price": 1455.27
}, {
"date": "2007-07-31T22:00:00.000Z",
"price": 1473.99
}, {
"date": "2007-08-31T22:00:00.000Z",
"price": 1526.75
}, {
"date": "2007-09-30T22:00:00.000Z",
"price": 1549.38
}, {
"date": "2007-10-31T23:00:00.000Z",
"price": 1481.14
}, {
"date": "2007-11-30T23:00:00.000Z",
"price": 1468.36
}, {
"date": "2007-12-31T23:00:00.000Z",
"price": 1378.55
}, {
"date": "2008-01-31T23:00:00.000Z",
"price": 1330.63
}, {
"date": "2008-02-29T23:00:00.000Z",
"price": 1322.7
}, {
"date": "2008-03-31T22:00:00.000Z",
"price": 1385.59
}, {
"date": "2008-04-30T22:00:00.000Z",
"price": 1400.38
}, {
"date": "2008-05-31T22:00:00.000Z",
"price": 1280
}, {
"date": "2008-06-30T22:00:00.000Z",
"price": 1267.38
}, {
"date": "2008-07-31T22:00:00.000Z",
"price": 1282.83
}, {
"date": "2008-08-31T22:00:00.000Z",
"price": 1166.36
}, {
"date": "2008-09-30T22:00:00.000Z",
"price": 968.75
}, {
"date": "2008-10-31T23:00:00.000Z",
"price": 896.24
}, {
"date": "2008-11-30T23:00:00.000Z",
"price": 903.25
}, {
"date": "2008-12-31T23:00:00.000Z",
"price": 825.88
}, {
"date": "2009-01-31T23:00:00.000Z",
"price": 735.09
}, {
"date": "2009-02-28T23:00:00.000Z",
"price": 797.87
}, {
"date": "2009-03-31T22:00:00.000Z",
"price": 872.81
}, {
"date": "2009-04-30T22:00:00.000Z",
"price": 919.14
}, {
"date": "2009-05-31T22:00:00.000Z",
"price": 919.32
}, {
"date": "2009-06-30T22:00:00.000Z",
"price": 987.48
}, {
"date": "2009-07-31T22:00:00.000Z",
"price": 1020.62
}, {
"date": "2009-08-31T22:00:00.000Z",
"price": 1057.08
}, {
"date": "2009-09-30T22:00:00.000Z",
"price": 1036.19
}, {
"date": "2009-10-31T23:00:00.000Z",
"price": 1095.63
}, {
"date": "2009-11-30T23:00:00.000Z",
"price": 1115.1
}, {
"date": "2009-12-31T23:00:00.000Z",
"price": 1073.87
}, {
"date": "2010-01-31T23:00:00.000Z",
"price": 1104.49
}, {
"date": "2010-02-28T23:00:00.000Z",
"price": 1140.45
}];
data = data.map(type);
console.log(data)
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.price;
})]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2);
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
focus.append("line")
.attr("class", "peak")
.style("stroke", "orange")
.attr("stroke-width", 3);
//});
function setPeak(){
var maxY = {
x: null,
y: -1e100
};
data.forEach(function(d){
if (d.date >= x.domain()[0] &&
d.date <= x.domain()[1] &&
d.price > maxY.y){
maxY.y = d.price;
maxY.x = d.date;
}
});
d3.select(".peak")
.attr("x1", x(maxY.x))
.attr("x2", x(maxY.x))
.attr("y1", 0)
.attr("y2", height);
}
setPeak();
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
setPeak();
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function type(d) {
d.date = new Date(d.date);
d.price = +d.price;
return d;
}
</script>

Having trouble converting a D3 v3 Force Directed graph into D3 v4 library implementation?

I've recently created my first force directed graph using v3 library, but now I'm required to create the same graph using D3 version 4 library, but the methods have changed tremendously in v4, and now I'm getting error at all force()/drag() methods of 3 that do not exist now in v4.
My graph is based on following mockup - http://www.ourd3js.com/wordpress/?p=606
Is there a repository of samples that have been created in v4 library of d3 someplace where I can take a look and learn few functions that I can replace with for this particular chart?
EDIT:
My current code looks like this, but I'm not able to convert it completely, for example, the node links are very close sometimes that text of links and nodes is overlapping.
<svg width="960" height="600"></svg>
Javascript Code:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var graph = root;
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
var width = x;
var height = y;
var img_w = 24;
var img_h = 24;
var k = Math.sqrt(root.nodes.length / (width * height));
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-5 / k))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("image")
.attr("width",img_w)
.attr("height",img_h)
.attr("xlink:href",function(d){
return d.image;
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var links_text = svg.selectAll(".linetext")
.data(graph.links)
.enter()
.append("text")
.attr("class","linetext slds-text-heading--small")
.attr("text-anchor", "middle")
.text(function(d){
return '['+d.relation+']';
});
var nodes_text = svg.selectAll(".nodetext")
.data(graph.nodes)
.enter()
.append("text")
.attr("class","nodetext slds-text-heading--label")
.attr("text-anchor", "middle")
.attr("dx",-20)
.attr("dy",20)
.text(function(d){
return (d.subname!=''?(d.subname+': '):'')+d.name;
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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; });
links_text
.attr("x",function(d){ return (d.source.x + d.target.x) / 2; })
.attr("y",function(d){ return (d.source.y + d.target.y) / 2; });
node
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
nodes_text
.attr("x",function(d){ return d.x + 20 })
.attr("y",function(d){ return d.y + img_w/2; });
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
The JSON Data String:
var root = {
"nodes" : [ {
"subname" : "",
"name" : "Telco Power Case",
"image" : "/node32.png",
"id" : 0
}, {
"subname" : "Contact",
"name" : "Suman Kumar",
"image" : "/subnode32.png.png",
"id" : 1
}, {
"subname" : "Contact",
"name" : "Karla Samuel",
"image" : "/subnode32.png.png",
"id" : 2
}, {
"subname" : "Account",
"name" : "Signa Tech",
"image" : "/subnode32.png.png",
"id" : 3
}, {
"subname" : "",
"name" : "Maven's Case",
"image" : "/node32.png",
"id" : 4
}, {
"subname" : "",
"name" : "Delta Case",
"image" : "/node32.png",
"id" : 5
}, {
"subname" : "Contact",
"name" : "T Browney",
"image" : "/subnode32.png.png",
"id" : 6
}, {
"subname" : "Account",
"name" : "Presto",
"image" : "/subnode32.png.png",
"id" : 7
}, {
"subname" : "Contact",
"name" : "Bob Tannenbaum",
"image" : "/subnode32.png.png",
"id" : 8
}, {
"subname" : "Account",
"name" : "Tesla Power",
"image" : "/subnode32.png.png",
"id" : 9
} ],
"links" : [ {
"target" : 1,
"source" : 0,
"relation" : "Trainee"
}, {
"target" : 2,
"source" : 0,
"relation" : "Manager"
}, {
"target" : 3,
"source" : 0,
"relation" : "Technology"
}, {
"target" : 1,
"source" : 0,
"relation" : "Trainee"
}, {
"target" : 2,
"source" : 0,
"relation" : "Manager"
}, {
"target" : 3,
"source" : 0,
"relation" : "Technology"
}, {
"target" : 2,
"source" : 4,
"relation" : "Expert"
}, {
"target" : 2,
"source" : 5,
"relation" : "Expert"
}, {
"target" : 1,
"source" : 5,
"relation" : "Expert"
}, {
"target" : 6,
"source" : 5,
"relation" : "Trainee"
}, {
"target" : 7,
"source" : 5,
"relation" : "Technology;New Firm"
}, {
"target" : 8,
"source" : 4,
"relation" : "Expert"
}, {
"target" : 9,
"source" : 4,
"relation" : "New Firm"
}, {
"target" : 8,
"source" : 4,
"relation" : "Expert"
}, {
"target" : 9,
"source" : 4,
"relation" : "New Firm"
}, {
"target" : 6,
"source" : 5,
"relation" : "Trainee"
}, {
"target" : 7,
"source" : 5,
"relation" : "Technology;New Firm"
} ]
};
You are asking a flurry of questions all at once, so let's get a little sanity on this question.
First, the linkDistance is now distance on the d3.forceLink, so in your code:
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(200))
Second, to center your image, do this when you set it's x position:
node
.attr("x", function(d) {
return (d.x - img_w /2);
});
Third, to do boundary detection you need to implement this yourself. For example, to fix the nodes position would be (building on last code snippet):
node
.attr("x", function(d) {
var xPos = (d.x - img_w /2);
if (xPos < 0) return 0;
if (xPos > (960 - img_w)) return (960 - img_w);
return xPos;
})
.attr("y", function(d) {
var yPos = d.y;
if (yPos < 0) return 0;
if (yPos > (600 - img_h)) return (600 - img_h);
return yPos;
});
Now apply the same methodology to the links...
Here's some example code where I've started to implement some fixes:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
<style>
.links line {
stroke: #aaa;
}
.nodes circle {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
</style>
</head>
<body>
<svg width="960" height="600"></svg>
<script>
var root = {
"nodes": [{
"subname": "",
"name": "Telco Power Case",
"image": "http://lorempixel.com/24/24/",
"id": 0
}, {
"subname": "Contact",
"name": "Suman Kumar",
"image": "http://lorempixel.com/24/24/",
"id": 1
}, {
"subname": "Contact",
"name": "Karla Samuel",
"image": "http://lorempixel.com/24/24/",
"id": 2
}, {
"subname": "Account",
"name": "Signa Tech",
"image": "http://lorempixel.com/24/24/",
"id": 3
}, {
"subname": "",
"name": "Maven's Case",
"image": "http://lorempixel.com/24/24/",
"id": 4
}, {
"subname": "",
"name": "Delta Case",
"image": "http://lorempixel.com/24/24/",
"id": 5
}, {
"subname": "Contact",
"name": "T Browney",
"image": "http://lorempixel.com/24/24/",
"id": 6
}, {
"subname": "Account",
"name": "Presto",
"image": "http://lorempixel.com/24/24/",
"id": 7
}, {
"subname": "Contact",
"name": "Bob Tannenbaum",
"image": "http://lorempixel.com/24/24/",
"id": 8
}, {
"subname": "Account",
"name": "Tesla Power",
"image": "http://lorempixel.com/24/24/",
"id": 9
}],
"links": [{
"target": 1,
"source": 0,
"relation": "Trainee"
}, {
"target": 2,
"source": 0,
"relation": "Manager"
}, {
"target": 3,
"source": 0,
"relation": "Technology"
}, {
"target": 1,
"source": 0,
"relation": "Trainee"
}, {
"target": 2,
"source": 0,
"relation": "Manager"
}, {
"target": 3,
"source": 0,
"relation": "Technology"
}, {
"target": 2,
"source": 4,
"relation": "Expert"
}, {
"target": 2,
"source": 5,
"relation": "Expert"
}, {
"target": 1,
"source": 5,
"relation": "Expert"
}, {
"target": 6,
"source": 5,
"relation": "Trainee"
}, {
"target": 7,
"source": 5,
"relation": "Technology;New Firm"
}, {
"target": 8,
"source": 4,
"relation": "Expert"
}, {
"target": 9,
"source": 4,
"relation": "New Firm"
}, {
"target": 8,
"source": 4,
"relation": "Expert"
}, {
"target": 9,
"source": 4,
"relation": "New Firm"
}, {
"target": 6,
"source": 5,
"relation": "Trainee"
}, {
"target": 7,
"source": 5,
"relation": "Technology;New Firm"
}]
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var graph = root;
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight || e.clientHeight || g.clientHeight;
var realWidth = width;
var width = x;
var height = y;
var img_w = 24;
var img_h = 24;
var k = Math.sqrt(root.nodes.length / (width * height));
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-5 / k))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("image")
.attr("width", img_w)
.attr("height", img_h)
.attr("xlink:href", function(d) {
return d.image;
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var links_text = svg.selectAll(".linetext")
.data(graph.links)
.enter()
.append("text")
.attr("class", "linetext slds-text-heading--small")
.attr("text-anchor", "middle")
.text(function(d) {
return '[' + d.relation + ']';
});
var nodes_text = svg.selectAll(".nodetext")
.data(graph.nodes)
.enter()
.append("text")
.attr("class", "nodetext slds-text-heading--label")
.attr("text-anchor", "middle")
.attr("dx", -20)
.attr("dy", 20)
.text(function(d) {
return (d.subname != '' ? (d.subname + ': ') : '') + d.name;
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) {
var xPos = d.source.x;
if (xPos < 0) return 0;
if (xPos > (960 - img_w)) return (960 - img_w);
return xPos;
})
.attr("y1", function(d) {
var yPos = d.source.y;
if (yPos < 0) return 0;
if (yPos > (600 - img_h)) return (600 - img_h);
return yPos;
})
.attr("x2", function(d) {
var xPos = d.target.x;
if (xPos < 0) return 0;
if (xPos > (960 - img_w)) return (960 - img_w);
return xPos;
})
.attr("y2", function(d) {
var yPos = d.target.y;
if (yPos < 0) return 0;
if (yPos > (600 - img_h)) return (600 - img_h);
return yPos;
});
links_text
.attr("x", function(d) {
var xPos = (d.source.x + d.target.x) / 2;
if (xPos < 0) return 0;
if (xPos > (960 - img_w)) return (960 - img_w);
return xPos;
})
.attr("y", function(d) {
var yPos = (d.source.y + d.target.y) / 2;
if (yPos < 0) return 0;
if (yPos > (600 - img_h)) return (600 - img_h);
return yPos;
});
node
.attr("x", function(d) {
var xPos = (d.x - img_w /2);
if (xPos < 0) return 0;
if (xPos > (960 - img_w)) return (960 - img_w);
return xPos;
})
.attr("y", function(d) {
var yPos = d.y;
if (yPos < 0) return 0;
if (yPos > (600 - img_h)) return (600 - img_h);
return yPos;
});
nodes_text
.attr("x", function(d) {
return d.x + 20
})
.attr("y", function(d) {
return d.y + img_w / 2;
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>

Appending multiline text to nodes in directed graphs in d3js

I am trying to create multi line text as nodes of the directed graph as :
<rect height="27" width="56" rx="100" ry="100" style="fill: #ffffff;"></rect>
<text dx="6" dy="6">
<tspan x="0" dy="15">Donovan</tspan>
<tspan x="0" dy="15">3</tspan>
<tspan x="0" dy="15">what</tspan>
</text>
as seen in: http://jsfiddle.net/nikosdim/S4eaL/1/
I currently have this:
// setting up parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 100;
var boxHeight = 50;
var boxWidth = 50;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -5000;
var gravity = 0.3;
var theta = 0.01;
var dataset = {
nodes: [
{ "name": "Adam", "id": "0" },
{ "name": "Bob", "id": "1" },
{ "name": "Carrie", "id": "2" },
{ "name": "Donovan", "id": "3" },
{ "name": "Edward", "id": "4" },
{ "name": "Felicity", "id": "5" },
{ "name": "George", "id": "6" },
{ "name": "Hannah", "id": "7" },
{ "name": "Iris", "id": "8" },
{ "name": "Jerry", "id": "9" }
],
edges: [
{ "source": 0, "target": 4 },
{ "source": 1, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 5, "target": 8 },
{ "source": 5, "target": 9 },
{ "source": 6, "target": 7 },
{ "source": 7, "target": 8 },
{ "source": 8, "target": 9 }
]
};
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#ccc")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(dataset.nodes)
.enter()
.append("rect")
.attr({ "width": boxWidth })
.attr({ "height": boxHeight })
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'white')
.attr('stroke', 'black')
.call(force.drag);
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({
//"x": function (d) { return d.x; },
//"y": function (d) { return d.y; },
"class": "nodelabel",
"stroke": "black"
});
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({
//'d': function (d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; },
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke', '#ccc');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
dataset.edges.forEach(function (d, i) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
});
// appending boxWidth/2 to make sure the labels are within the box
nodelabels.attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2; })
.attr("y", function (d) { return ConstrainY(d.y); });
edgepaths.attr('d', function (d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
});
edgelabels.attr('transform', function (d, i) {
if (d.target.x < d.source.x) {
bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
var insertLinebreaks = function (d) {
var el = d3.select(this);
var name = d.name;
var id = d.id;
el.text('');
//for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(name);
tspan = el.append('tspan').text(id);
//if (i > 0)
tspan.attr('x', 0);
tspan.attr('dy', '15');
tspan = el.append('tspan').text('what');
tspan.attr('x', '0');
tspan.attr('dy', '15');
//}
};
nodelabels.each(insertLinebreaks); <== Insert new lines
}
But this is messing new lines in the nodes. Once I insert the new lines the text shows up left aligned at the start of the screen. This is not what I want. I wanted the text to be aligned in the node as shown in the first image.
This is the output using the above code:
http://jsfiddle.net/6afc2vp8/
// **********start configuration***************
var dataset = {
"Nodes": [
{
"ID": 0,
"Identity": "af12689c-de83-4a0d-a63d-1f548fd02e26",
"RoleInstance": "RoleInstance",
"LogonID": "LogonID1",
"WeightedScore": 120,
"AccountInstance": "AccountInstance1",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 1,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 2,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 3,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
}
],
"Edges": [
{
"source": 0,
"target": 1
}, {
"source": 3,
"target": 2
}
]
};
// select all the features from the node that you want in the output
var nodelabelslist = ["ID", "Identity", "RoleInstance", "LogonID", "WeightedScore", "AccountInstance", "MinTimestampPst"];
var nodelabelslistlength = nodelabelslist.length;
// this is the rectangle height and width
var boxHeight = nodelabelslistlength * 20;
var boxWidth = 400;
var nodelabelverticaloffset = 15;
var nodelabelhorizontaloffset = 5;
var labelStartPos = -(nodelabelslistlength/2 - 1);
// setting up d3js parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 200;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -6000;
var gravity = 0.10;
var theta = 0.01;
// **********end configuration***************
var jsonNodes = dataset.Nodes;
var jsonEdges = dataset.Edges;
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(jsonNodes)
.links(jsonEdges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(jsonEdges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#000")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(jsonNodes)
.enter()
.append("rect")
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'none')
.attr('stroke', 'black')
.call(force.drag);
var nodelabelsarr = [];
for (var index = 0; index < nodelabelslist.length; ++index) {
var nodelabels = svg.selectAll(".nodelabel" + index)
.data(jsonNodes)
.enter()
.append("text")
.attr({
"class": "nodelabel",
"stroke": "black"
})
.text(function (d) { return nodelabelslist[index] + ": " + d[nodelabelslist[index]]; });
nodelabelsarr[index] = nodelabels;
}
var edgepaths = svg.selectAll(".edgepath")
.data(jsonEdges)
.enter()
.append('path')
.attr({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(jsonEdges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#000'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#000')
.attr('stroke', '#000');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function edgeArc(d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
}
function edgelabeltransform(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
var rx = bbox.x + bbox.width / 2;
var ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
jsonEdges.forEach(function (d) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
})
.attr({ "width": boxWidth })
.attr({ "height": boxHeight });
// appending boxWidth/2 to make sure the labels are within the box
var startOffset = labelStartPos;
for (var index = 0; index < nodelabelsarr.length; index++) {
nodelabelsarr[index].attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2 + nodelabelhorizontaloffset; })
.attr("y", function (d) { return ConstrainY(d.y) + (nodelabelverticaloffset * startOffset); });
startOffset++;
}
edgepaths.attr('d', edgeArc);
edgelabels.attr('transform', edgelabeltransform);
}

Resources