I have built a force-directed graph, however, in my case, I have also a grid with 80px*80px boxes. I'd like that each node in graph was positioned not only according to existing gravity and forces, but also in the middle of the closest grid box (without being fixed).
Is it possible to do this in d3js?
Moritz Stefaner came up with a way to do this
code: https://github.com/moritzstefaner/gridexperiments/
demo: http://moritzstefaner.github.io/gridexperiments/
EDIT:
as mentioned by #altocumulus, this didn't have a copy of the code. Normally I only copy the code from individual's sites as they're much more likely to disappear than something on github. Or I'll copy it when it is short (less than 50 loc?). Anyway, since the meat of the code can probably be pulled out, I've copied mortiz's index.html file below. the other referenced js files can be found elsewhere very easily. (just note that you should probably pull the versions of each library as of Dec 9, 2011)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta charset="utf-8">
<title>Forces and grids</title>
<script type="text/javascript" src="d3.min.js"></script>
<script type="text/javascript" src="d3.layout.min.js"></script>
<script type="text/javascript" src="d3.geom.min.js"></script>
<script type="text/javascript" src="underscore-min.js"></script>
<script src="jquery-1.7.2.min.js" charset="utf-8"></script>
<style type="text/css" media="screen">
.menu { position:absolute; top :20px; right:20px; }
</style>
</head>
<body>
<script type="text/javascript" charset="utf-8">
var w = 700, h = 700;
var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h);
var background = vis.append("g");
var nodes = [];
var links = [];
var USE_GRID = true;
var GRID_SIZE = 60;
var GRID_TYPE = "HEXA";
// set up event handlers
$(document).ready(function(){
$("#USE_GRID").click(
function(){
USE_GRID = $(this).is(":checked");
$(this).blur();
force.start();
}
);
//$("#CELL_SIZE").rangeinput();
$("#CELL_SIZE").bind("change",
function(){
console.log($(this).attr("value"));
GRID_SIZE = $(this).attr("value");
grid.init();
force.start();
}
);
$("[name=GRID_TYPE]").click(
function(){
GRID_TYPE = $(this).attr("value");
grid.init();
force.start();
}
);
});
for(var i = 0; i < 30; i++) {
var node = {
label : "node " + i
};
nodes.push(node);
};
for(var i = 0; i < nodes.length; i++) {
for(var j = 0; j < i; j++) {
if(Math.random() > .99-Math.sqrt(i)*.02)
links.push({
source : i,
target : j,
weight :1
});
}
};
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(function(d){return (1-d.weight)*100}).charge(-3000).linkStrength(function(x) {
return x.weight * 5
});
force.start();
var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke-width", 1.5).style("stroke", "#555").style("opacity", function(d){return d.weight*.7});
var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node");
node.append("svg:circle").attr("r", 6).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", "4px");
node.call(force.drag);
var updateLink = function() {
this.attr("x1", function(d) {
return d.source.screenX;
}).attr("y1", function(d) {
return d.source.screenY;
}).attr("x2", function(d) {
return d.target.screenX;
}).attr("y2", function(d) {
return d.target.screenY;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
if(USE_GRID) {
var gridpoint = grid.occupyNearest(d);
if(gridpoint) {
d.screenX = d.screenX || gridpoint.x;
d.screenY = d.screenY || gridpoint.y;
d.screenX += (gridpoint.x - d.screenX) * .2;
d.screenY += (gridpoint.y - d.screenY) * .2;
d.x += (gridpoint.x - d.x) * .05;
d.y += (gridpoint.y - d.y) * .05;
}
} else {
d.screenX = d.x;
d.screenY = d.y;
}
return "translate(" + d.screenX + "," + d.screenY + ")";
});
};
var grid = function(width, height) {
return {
cells : [],
init : function() {
this.cells = [];
for(var i = 0; i < width / GRID_SIZE; i++) {
for(var j = 0; j < height / GRID_SIZE; j++) {
// HACK: ^should be a better way to determine number of rows and cols
var cell;
switch (GRID_TYPE) {
case "PLAIN":
cell = {
x : i * GRID_SIZE,
y : j * GRID_SIZE
};
break;
case "SHIFT_ODD_ROWS":
cell = {
x : i * GRID_SIZE,
y : 1.5 * (j * GRID_SIZE + (i % 2) * GRID_SIZE * .5)
};
break;
case "HEXA":
cell = {
x : i * GRID_SIZE + (j % 2) * GRID_SIZE * .5,
y : j * GRID_SIZE * .85
};
break;
}
this.cells.push(cell);
};
};
},
sqdist : function(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
},
occupyNearest : function(p) {
var minDist = 1000000;
var d;
var candidate = null;
for(var i = 0; i < this.cells.length; i++) {
if(!this.cells[i].occupied && ( d = this.sqdist(p, this.cells[i])) < minDist) {
minDist = d;
candidate = this.cells[i];
}
}
if(candidate)
candidate.occupied = true;
return candidate;
}
}
}(w, h);
force.on("tick", function() {
vis.select("g.gridcanvas").remove();
if(USE_GRID) {
grid.init();
var gridCanvas = vis.append("svg:g").attr("class", "gridcanvas");
_.each(grid.cells, function(c) {
gridCanvas.append("svg:circle").attr("cx", c.x).attr("cy", c.y).attr("r", 2).style("fill", "#555").style("opacity", .3);
});
}
node.call(updateNode);
link.call(updateLink);
});
</script>
<div class="menu">
<div>
<input type="checkbox" id="USE_GRID" checked>use grid</input>
</div>
<div>
<input type="range" min="30" step="10" max="150" id="CELL_SIZE" value="60"></input>
</div>
<div>
<input type="radio" name="GRID_TYPE" value="PLAIN">plain</input>
<input type="radio" name="GRID_TYPE" value="SHIFT_ODD_ROWS">Shift odd rows</input>
<input type="radio" name="GRID_TYPE" value="HEXA" checked>Hexa</input>
</div>
</div>
</body>
You have to apply your custom forces in
force.on("tick", function() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});
so there is no built-in way to do such a thing...
So in your case you have to find the close grid box centers and count the x and y values using the distance between the nodes and the box centers and some gravity equation.
In you case
node
.attr("cx", function(d) {
d.x += f(d).x;
return d.x;
})
.attr("cy", function(d) {
d.y += f(d).y;
return d.y;
});
where f(d) is the vector of your gravity force depends on the distance between the box centers and the actual node d. For example
var blackHole = function (d) {
var gc = {
x: 100,
y: 100
};
var k = 0.1;
var dx = gc.x - d.px;
var dy = gc.y - d.py;
return {
x: k * dx,
y: k * dy
};
};
It is pretty hard to find out an f(d) which really works by multiple gravity centers, so I suggest you to read about such force algorithms. I tried out some funny examples, but none of them works the way you want. ;-)
Now at least:
var grid = function (d) {
var fx = d.px % 100;
if (fx < 0)
fx += 100;
if (fx > 50)
fx -= 100;
var fy = d.py % 100;
if (fy < 0)
fy += 100;
if (fy > 50)
fy -= 100;
var k = -1;
return {
x: k * fx,
y: k * fy
};
};
This is a 100px dense grid with very simple forces... But I guess the result is not what you expected, nodes can overlap, because by force layout only nodes with common links repel each other, at least that is my experience (edit: that's because the negative charge)... I think is could be much easier to build a custom force layout using d3 quad...
Related
Below is an HTML file that will draw a 10x10 grid of squares alternating light grey and dark grey. It fills a 2d array called the_grid with 0 or 1; then fills a 1d array called nodes with x, y, and color; then draws nodes with d3. They all appear. They look like this:
How do I instead have nodes drawn (i.e. appear) one at a time, in the order given by the nodes array (so I could draw different patterns, say vertical wipe, horizontal wipe, whatever)?
I tried fiddling with the transition function without success. It just draws the whole grid, and slides it into place. The squares don't visibly appear one by one.
The code:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script>
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
</script>
<style>rect { color: black; }</style>
</head>
<body><div id="graph"/></body>
</html>
To stagger the transitions of multiple elements entered at the same time from the same data array you can use transition.delay(), you can specify a constant (which will start all transitions simutaneously) or you can specify a function to base the delay on the datum of each element or its index:
selection.transition()
.delay(function(d,i) { return i * 100; })
.attr(...
Above and in the snippet below I've used the index:
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill","white")
.transition()
.duration(1000)
.delay(function(d,i) { return i * 100; })
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph"/>
And below I've used the datum to create a random transition order:
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {
delay: Math.random()*2000,
x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill","white")
.transition()
.duration(1000)
.delay(function(d,i) { return d.delay; })
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph"/>
I started working with this d3.js Donut Chart: JSFiddleI am trying to change it into a Pie Chart without the circle in the middle. I am new to d3.js. I have tried several different ideas but have been unable to get this to remove the circle in the middle of the chart. Any and all help is appreciated
Here is my code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style>
.label-text {
alignment-baseline : middle;
font-size: 12px;
font-family: arial,helvetica,"sans-serif";
fill: #393939;
}
.label-line {
stroke-width: 1;
stroke: #393939;
}
.label-circle {
fill: #393939;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg>
<g id="canvas">
<g id="art" />
<g id="labels" /></g>
</svg>
<script>
var data = [{
label: 'Star Wars',
instances: 207
}, {
label: 'Lost In Space',
instances: 3
}, {
label: 'the Boston Pops',
instances: 20
}, {
label: 'Indiana Jones',
instances: 150
}, {
label: 'Harry Potter',
instances: 75
}, {
label: 'Jaws',
instances: 5
}, {
label: 'Lincoln',
instances: 1
}];
svg = d3.select("svg");
canvas = d3.select("#canvas");
art = d3.select("#art");
labels = d3.select("#labels");
// Create the pie layout function.
// This function will add convenience
// data to our existing data, like
// the start angle and end angle
// for each data element.
jhw_pie = d3.layout.pie();
jhw_pie.sort(null);
jhw_pie.value(function (d) {
// Tells the layout function what
// property of our data object to
// use as the value.
return d.instances;
});
// Store our chart dimensions
cDim = {
height: 500,
width: 500,
innerRadius: 50,
outerRadius: 150,
labelRadius: 175
}
// Set the size of our SVG element
svg.attr({
height: cDim.height,
width: cDim.width
});
// This translate property moves the origin of the group's coordinate
// space to the center of the SVG element, saving us translating every
// coordinate individually.
canvas.attr("transform", "translate(" + (cDim.width / 2) + "," + (cDim.height / 2) + ")");
pied_data = jhw_pie(data);
// The pied_arc function we make here will calculate the path
// information for each wedge based on the data set. This is
// used in the "d" attribute.
pied_arc = d3.svg.arc()
.innerRadius(50)
.outerRadius(150);
// This is an ordinal scale that returns 10 predefined colors.
// It is part of d3 core.
pied_colors = d3.scale.ordinal()
.range(["#04B486", "#F2F2F2", "#F5F6CE", "#00BFFF","orange","purple","pink"]);
// Let's start drawing the arcs.
enteringArcs = art.selectAll(".wedge").data(pied_data)
.enter();
enteringArcs
.append("g")
.attr("class", "wedge")
.append("path")
.attr("d", pied_arc)
.style("fill", function (d, i) {
return pied_colors(i);
});
// Now we'll draw our label lines, etc.
enteringLabels = labels.selectAll(".label").data(pied_data).enter();
labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr({
x: 0,
y: 0,
r: 2,
fill: "#000",
transform: function (d, i) {
centroid = pied_arc.centroid(d);
return "translate(" + pied_arc.centroid(d) + ")";
},
'class': "label-circle"
});
// "When am I ever going to use this?" I said in
// 10th grade trig.
textLines = labelGroups.append("line").attr({
x1: function (d, i) {
return pied_arc.centroid(d)[0];
},
y1: function (d, i) {
return pied_arc.centroid(d)[1];
},
x2: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
return x;
},
y2: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'class': "label-line"
});
textLabels = labelGroups.append("text").attr({
x: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
sign = (x > 0) ? 1 : -1
labelX = x + (5 * sign)
return labelX;
},
y: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'text-anchor': function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
return (x > 0) ? "start" : "end";
},
'class': 'label-text'
}).text(function (d) {
return d.data.label
});
alpha = 0.5;
spacing = 12;
function relax() {
again = false;
textLabels.each(function (d, i) {
a = this;
da = d3.select(a);
y1 = da.attr("y");
textLabels.each(function (d, j) {
b = this;
// a & b are the same element and don't collide.
if (a == b) return;
db = d3.select(b);
// a & b are on opposite sides of the chart and
// don't collide
if (da.attr("text-anchor") != db.attr("text-anchor")) return;
// Now let's calculate the distance between
// these elements.
y2 = db.attr("y");
deltaY = y1 - y2;
// Our spacing is greater than our specified spacing,
// so they don't collide.
if (Math.abs(deltaY) > spacing) return;
// If the labels collide, we'll push each
// of the two labels up and down a little bit.
again = true;
sign = deltaY > 0 ? 1 : -1;
adjust = sign * alpha;
da.attr("y", +y1 + adjust);
db.attr("y", +y2 - adjust);
});
});
// Adjust our line leaders here
// so that they follow the labels.
if (again) {
labelElements = textLabels[0];
textLines.attr("y2", function (d, i) {
labelForLine = d3.select(labelElements[i]);
return labelForLine.attr("y");
});
setTimeout(relax, 20)
}
}
relax();
</script>
</body>
</html>
Thanks
See this updated fiddle.
The code contained the following lines, of which the innerRadious was changed to 0.
pied_arc = d3.svg.arc()
.innerRadius(00) // <- this
.outerRadius(150);
It's a bit misleading, as there's an innerRadius variable somewhere before that, but it's not used at this point. While you're at it, you might want to align all of that stuff.
I just got started with D3's forced layout and implemented a radial graph implementation by referring : http://flowingdata.com/2012/08/02/how-to-make-an-interactive-network-visualization/
The problem I am facing is that the zooming is not working. Here's the code I referred: http://jsfiddle.net/blt909/aVhd8/20/ for zoom functionality
And here's my code:
var w = $("#network-plot").width(),
h = 800,
r = 6,
fill = d3.scale.category20();
var payload={js:'mis1'}
$.ajax({
url: "/getSource",
type: "post",
async: false,
data: payload,
success: function (data) {
mis1=JSON.parse(data);
},
});
var force = d3.layout.force()
.charge(-30)
.linkDistance(310)
.size([w, h]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svgnt = d3.select("#network-plot-svg")
.attr("width", w)
.attr("height", h)
.style('margin-top', h/15)
.call(zoom);
var vis = svgnt.append("svg:g");
var rect = vis.append("svg:rect")
.attr("width", w)
.attr("height", h)
.attr("fill", '#fff')
.style("pointer-events", "all");
function zoomed() {
vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var radius = d3.scale.log();
var mis1;
//**********RADIAL CODE****************
var unsortednodes = {};
var sortednodes = [];
var sortagainst = [];
var heaviest_node_length = 0;
for(var i = 0; i < mis1.nodes.length; i++) {
var count_conn = 0;
var heaviest_node;
for(var j = 0; j < mis1.links.length; j++) {
if(mis1.links[j].source == mis1.nodes[i].group || mis1.links[j].target == mis1.nodes[i].group) {
count_conn++;
}
}
if(count_conn > heaviest_node_length) {
heaviest_node_length = count_conn;
heaviest_node = i;
}
unsortednodes[i] = 10 + count_conn;
sortagainst[i] = 10 + count_conn;
}
sortagainst.sort();
sortagainst.reverse();
for(i in unsortednodes) {
var index = sortagainst.indexOf(unsortednodes[i]);
sortednodes[index] = i;
sortagainst[index] = '';
}
var c = {"x":w/2, "y":h/2};
var negativeY = 0;
var positiveY = 0;
var negativeX = 0;
var positiveX = 0;
var groupcenters = radialPlacement(c, 300, 18, sortednodes);
//following lines for readjustment code in case svg container is outgrown by graph
h = Math.abs(negativeY) + Math.abs(positiveY) + 100; //TBD: extra padding needs to be dynamic
w = Math.abs(negativeX) + Math.abs(positiveX) + 200;
c = {"x":w/2, "y":h/2};
groupcenters = radialPlacement(c, 300, 18, sortednodes);
svgnt.attr("height", h);
vis.attr("height", h);
svgnt.attr("width", w);
vis.attr("width", w);
function radialLocation(center, angle, radius) {
x = (center.x + radius * Math.cos(angle * Math.PI / 180));
y = (center.y + radius * Math.sin(angle * Math.PI / 180));
if(y < negativeY) {
negativeY = y;
}
if(y > positiveY) {
positiveY = y;
}
if(x < negativeX) {
negativeX = x;
}
if(x > positiveX) {
positiveX = x;
}
return {"x":x,"y":y};
}
function radialPlacement(center, radius, increment, keys) {
var values_circle = {};
var increment;
var start = -90;
var current = start;
var total_nodes = keys.length;
var circles = Math.floor(Math.sqrt((total_nodes - 1)/5));
if(circles == 0) {
circles = 1;
}
var ratio;
var r = [];
var circleKeys = [];
var radius = 140;
r[0] = radius;
var sum_r = r[0];
for(var j = 1; j < circles; j++){
r[j] = r[j-1] + 100 //TBD: should radius be linearly incremented like this?
sum_r += r[j];
}
ratio = 1/sum_r;
var temp = 0;
for(j = 0; j < circles; j++) {
if(j == circles - 1)
circleKeys[j] = total_nodes - temp;
else {
circleKeys[j] = Math.floor(total_nodes * r[j] * ratio);
temp += circleKeys[j];
}
}
var k = 0;
for(var i = 0; i < circleKeys.length; i++) {
increment = 360/circleKeys[i];
for(j = 0; j < circleKeys[i]; j++, k++) {
if(k == 0) {
values_circle[keys[k]] = radialLocation(center, -90, 0);
}
else {
values_circle[keys[k]] = radialLocation(center, current, r[i]);;
current += increment;
}
}
}
return values_circle;
}
//************RADIAL CODE ENDS***************
d3.json(mis1, function() {
var link = svgnt.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = svgnt.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
.attr("r", function(d, j){
var count = 0;
for(var i=0; i<mis1.links.length; i++) {
if(mis1.links[i].source == d.group || mis1.links[i].target == d.group){
count ++;
}
}
return (10+count);
})
.style("fill", function(d) {
return fill(d.group);
})
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
texts = svgnt.selectAll("text.label")
.data(mis1.nodes)
.enter().append("text")
.attr("class", "label")
.attr("fill", "black")
.text(function(d) { return d.name; });
force.nodes(mis1.nodes).links(mis1.links).on("tick", tick).start();
var linkedByIndex = {};
mis1.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function tick() {
node.attr("cx", function(d, i) {
return d.x = groupcenters[i].x;
}).attr("cy", function(d, i) {
return d.y = groupcenters[i].y;
});
link.attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.target.x;
}).attr("y2", function(d) {
return d.target.y;
});
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
Any idea what I am doing wrong? The graph is displayed fine but doesn't have any zoom capability.
The problem is, you are appending all nodes and links directly to the svg and not to the g element. Since zoom transformations are applied to the vis (g element), nodes and links will not zoom/pan.
So instead of below code
var link = svgnt.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = svgnt.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
----------------------
----------------------
----------------------
try this code.
var link = vis.selectAll("line")
.data(mis1.links)
.enter()
.append("svg:line")
.style("stroke","#ddd");
var node = vis.selectAll("circle")
.data(mis1.nodes)
.enter()
.append("svg:circle")
----------------------
----------------------
----------------------
Hello I need help to add gray (Minor and Major) graduation lines inside the gauge to capture more precisely the position of the needle. Also if you have any idea how we could put text to Major graduation lines.
For my sample I would required 7 Major graduation lines divided by 10 minor separations (to look exactly like the Telerik Gauge).
Here is my implementation : http://jsfiddle.net/8svg5/
<html>
<head runat="server">
<title></title>
</head>
<body>
<div>
<a id="myTooltip" title="This is my message"></a>
<div id="svgTarget"></div>
</div>
<script>
$(function () {
var gaugeRanges = [
{
From: 1.5,
To: 2.5,
Color: "#8dcb2a"
}, {
From: 2.5,
To: 3.5,
Color: "#ffc700"
}, {
From: 3.5,
To: 4.5,
Color: "#ff7a00"
},
{
From: 4.5,
To: 6,
Color: "#c20000"
}];
$("#svgTarget").mttD3Gauge({ data: gaugeRanges });
});
</script>
</body>
</html>
(function ($) {
$.fn.mttD3Gauge = function (options) {
var settings = $.extend({
width: 300,
innerRadius: 130,
outterRadius: 145,
data: []
}, options);
this.create = function () {
this.html("<svg class='mtt-svgClock' width='" + settings.width + "' height='" + settings.width + "'></svg>");
var maxLimit = 0;
var minLimit = 9999999;
var d3DataSource = [];
var d3TickSource = [];
//Data Genration
$.each(settings.data, function (index, value) {
d3DataSource.push([value.From, value.To, value.Color]);
if (value.To > maxLimit) maxLimit = value.To;
if (value.From < minLimit) minLimit = value.From;
});
if (minLimit > 0) {
d3DataSource.push([0, minLimit, "#d7d7d7"]);
}
var pi = Math.PI;
//Control Genration
var vis = d3.select(this.selector + " .mtt-svgClock");
var translate = "translate(" + settings.width / 2 + "," + settings.width / 2 + ")";
var cScale = d3.scale.linear().domain([0, maxLimit]).range([-120 * (pi / 180), 120 * (pi / 180)]);
var arc = d3.svg.arc()
.innerRadius(settings.innerRadius)
.outerRadius(settings.outterRadius)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
var tickArc = d3.svg.arc()
.innerRadius(settings.innerRadius - 20)
.outerRadius(settings.innerRadius - 2)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
for (var i = 0; i < 10; i++) {
var point = (i * maxLimit) / 10.0;
d3TickSource.push([point, point +1, "#d7d7d7"]);
}
vis.selectAll("path")
.data(d3DataSource)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function (d) { return d[2]; })
.attr("transform", translate);
return this;
};
return this.create();
};
}(jQuery));
enter code here
Here is the link of the page of what i'm trying to achieve with D3.js
http://demos.telerik.com/aspnet-ajax/gauge/examples/types/radialgauge/defaultcs.aspx?#qsf-demo-source
Any help will be greatly appreciated!
I finally found the way to add graduation, feel free to use this code
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="Scripts/jquery-1.9.1.js"></script>
<script src="Scripts/html5shiv.js"></script>
<script src="Scripts/d3.v3.min.js"></script>
<script src="Scripts/mtt-D3Gauge.js"></script>
</head>
<body>
<div>
<a id="myTooltip" title="This is my message"></a>
<div id="svgTarget"></div>
</div>
<script>
$(function () {
var gaugeRanges = [
{
From: 1.5,
To: 2.5,
Color: "#8dcb2a"
}, {
From: 2.5,
To: 3.5,
Color: "#ffc700"
}, {
From: 3.5,
To: 4.5,
Color: "#ff7a00"
},
{
From: 4.5,
To: 6,
Color: "#c20000"
}];
$("#svgTarget").mttD3Gauge({ data: gaugeRanges });
});
</script>
</body>
</html>
(function ($) {
$.fn.mttD3Gauge = function (options) {
var vis;
var settings = $.extend({
width: 300,
innerRadius: 130,
outterRadius: 145,
majorGraduations: 6,
minorGraduations: 10,
majorGraduationLenght: 16,
minorGraduationLenght: 10,
majorGraduationMarginTop: 7,
majorGraduationColor: "rgb(234,234,234)",
minorGraduationColor:"rgb(234,234,234)",
data: []
}, options);
this.create = function () {
this.html("<svg class='mtt-svgClock' width='" + settings.width + "' height='" + settings.width + "'></svg>");
var maxLimit = 0;
var minLimit = 9999999;
var d3DataSource = [];
//Data Genration
$.each(settings.data, function (index, value) {
d3DataSource.push([value.From, value.To, value.Color]);
if (value.To > maxLimit) maxLimit = value.To;
if (value.From < minLimit) minLimit = value.From;
});
if (minLimit > 0) {
d3DataSource.push([0, minLimit, "#d7d7d7"]);
}
//Render Gauge Color Area
vis = d3.select(this.selector + " .mtt-svgClock");
var translate = "translate(" + settings.width / 2 + "," + settings.width / 2 + ")";
var cScale = d3.scale.linear().domain([0, maxLimit]).range([-120 * (Math.PI / 180), 120 * (Math.PI / 180)]);
var arc = d3.svg.arc()
.innerRadius(settings.innerRadius)
.outerRadius(settings.outterRadius)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
vis.selectAll("path")
.data(d3DataSource)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function (d) { return d[2]; })
.attr("transform", translate);
renderMajorGraduations();
return this;
};
var renderMinorGraduations = function (majorGraduationsAngles, indexMajor) {
var graduationsAngles = [];
if (indexMajor > 0) {
var minScale = majorGraduationsAngles[indexMajor - 1];
var maxScale = majorGraduationsAngles[indexMajor];
var scaleRange = maxScale - minScale;
for (var i = 1; i < settings.minorGraduations; i++) {
var scaleValue = minScale + i * scaleRange / settings.minorGraduations;
graduationsAngles.push(scaleValue);
}
var xCenter = settings.width / 2;
var yCenter = settings.width / 2;
//Render Minor Graduations
$.each(graduationsAngles, function (indexMinor, value) {
var cos1Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.minorGraduationLenght));
var sin1Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.minorGraduationLenght));
var cos2Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var sin2Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var x1 = xCenter + cos1Adj;
var y1 = yCenter + sin1Adj * -1;
var x2 = xCenter + cos2Adj;
var y2 = yCenter + sin2Adj * -1;
vis.append("svg:line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.style("stroke", settings.majorGraduationColor);
});
}
};
var renderMajorGraduations = function () {
var scaleRange = 240;
var minScale = -120;
var graduationsAngles = [];
for (var i = 0; i <= settings.majorGraduations; i++) {
var scaleValue = minScale + i * scaleRange / settings.majorGraduations;
graduationsAngles.push(scaleValue);
}
var xCenter = settings.width / 2;
var yCenter = settings.width / 2;
//Render Major Graduations
$.each(graduationsAngles, function (index, value) {
var cos1Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.majorGraduationLenght));
var sin1Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.majorGraduationLenght));
var cos2Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var sin2Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var x1 = xCenter + cos1Adj;
var y1 = yCenter + sin1Adj * -1;
var x2 = xCenter + cos2Adj;
var y2 = yCenter + sin2Adj * -1;
vis.append("svg:line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.style("stroke", settings.majorGraduationColor);
renderMinorGraduations(graduationsAngles, index);
});
};
return this.create();
};
}(jQuery));
I'm working with a D3 example file for a force directed voronoi graph... however, I mainly just needed a simplified version with only three vertices... so I simplified the file and have included a JSFiddle example of where my file stands currently. My issue is how the edge condition is handled. Right now, the voronoi edges extend out to the edge of the container div. However, I'd like to clip each voronoi cell by a circle boundary. I've included two images to help explain my problem. The first image shows the script as it exists now, whereas the second is made with photoshop - showing the circular clipping boundary. It seems like D3's polygon.clip method would be the best option, but I don't really know how to implement the clipping method into my script. Any suggestions would be greatly appreciated.
<!DOCTYPE html>
<html>
<head>
<title>Voronoi Diagram with Force Directed Nodes and Delaunay Links</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
path
{
stroke: #EFEDF5;
stroke-width: 4px;
}
</style>
</head>
<body>
<div id="chart">
</div>
<script type="text/javascript">
var w = window.innerWidth > 960 ? 960 : (window.innerWidth || 960),
h = window.innerHeight > 500 ? 500 : (window.innerHeight || 500),
radius = 5.25,
links = [],
simulate = true,
zoomToAdd = true,
cc = ["#FFA94A","#F58A3A","#F85A19"]
var numVertices = (w*h) / 200000;
var vertices = d3.range(numVertices).map(function(i) {
angle = radius * (i+10);
return {x: angle*Math.cos(angle)+(w/2), y: angle*Math.sin(angle)+(h/2)};
});
var d3_geom_voronoi = d3.geom.voronoi().x(function(d) { return d.x;}).y(function(d) { return d.y; })
var prevEventScale = 1;
var zoom = d3.behavior.zoom().on("zoom", function(d,i) {
if (zoomToAdd){
if (d3.event.scale > prevEventScale) {
angle = radius * vertices.length;
}
force.nodes(vertices).start()
} else {
if (d3.event.scale > prevEventScale) {
radius+= .01
} else {
radius -= .01
}
vertices.forEach(function(d, i) {
angle = radius * (i+10);
vertices[i] = {x: angle*Math.cos(angle)+(w/2), y: angle*Math.sin(angle)+(h/2)};
});
force.nodes(vertices).start()
}
prevEventScale = d3.event.scale;
});
d3.select(window)
.on("keydown", function() {
// shift
if(d3.event.keyCode == 16) {
zoomToAdd = false
}
})
.on("keyup", function() {
zoomToAdd = true
})
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h)
.call(zoom)
var force = d3.layout.force()
.charge(-300)
.size([w, h])
.on("tick", update);
force.nodes(vertices).start();
var path = svg.selectAll("path");
function update(e) {
path = path.data(d3_geom_voronoi(vertices))
path.enter().append("path")
// drag node by dragging cell
.call(d3.behavior.drag()
.on("drag", function(d, i) {
vertices[i] = {x: vertices[i].x + d3.event.dx, y: vertices[i].y + d3.event.dy}
})
)
.style("fill", function(d, i) { return cc[0] })
path.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.transition().duration(150)
.style("fill", function(d, i) { return cc[i] })
path.exit().remove();
if(!simulate) force.stop()
}