This is a follow on from my previous question
d3 rect in one group interfering with rect in another group
Two issues:
Incorrect drag behaviour. When clicking on the second rect to drag it, it jumps to where the third one is.
I added a anonymous function which runs when the svg in clicked on anywhere. This should add a new rect. However that is the working.
I know I should have only one issue per question but these are related and I suspect they will be solved together.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
// create group
var group = svg.selectAll("g")
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 200)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
Here are the explanations to your issues:
You are reassigning var groups, that is, you have two var groups in your code, the last one overwriting the first one. Just remove the last variable.
In your function to append new rectangles, you are using an update selection that selects rectangles. However, your enter selection appends groups (<g>) elements, not rectangles.
Have a look at the refactored function, it binds the data to a newly created group and appends the rectangle to that group:
var newGroup = svg.selectAll(".group")
.data(data, function(d) {
return d.x
})
.enter()
.append("g")
//etc...
newGroup.append("rect")
//etc...
Also, use a key selection in the data binding, so you know exactly what rectangle is being dragged:
.data(data, function(d){return d.x})
Here is your code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data, function(d){return d.x})
.enter().append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: coords[0],
}
data.push(newData);
var newGroup = svg.selectAll(".group")
.data(data, function(d){return d.x})
.enter()
.append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
newGroup.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
For correctly drag-and-drop behavior, rewrite your code like this:
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.exit()
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script>
<svg width="960" height="500"></svg>
But, what the problem with adding the new element, I have no idea.
Related
I asked this question yesterday and got a great duplicate question answer - many thanks Gerardo.
The example used images with transparent backgrounds (see Opera and Chrome image below) and it works a treat however, I would like to provide square images which fit inside the circle.
I've tried the border radius in CSS and Bootstrap circular image but these don't work - probably because it is an tag not an tag.
Here is the rendered tag in case that is helpful.
<image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="female1.png" class="circle-image" height="40" width="40" x="-20" y="-20"></image>
Would setting it up as a pattern help?
You can use a SVG pattern:
var defs = svg.append("defs");
defs.append('pattern')
.attr("id", "foo")
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", "foo.jpg")
.attr("width", someValue)
.attr("height", someValue)
.attr("y", someValue)
.attr("x", someValue);
Then, in your circles:
.attr("fill", "url(#foo")
Here is a demo:
var width = 400;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var defs = svg.append("defs");
defs.append('pattern')
.attr("id", "dog")
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", "http://cdn2-www.dogtime.com/assets/uploads/2010/12/senior-dog-2.jpg")
.attr("width", 100)
.attr("height", 100)
.attr("y", -20)
.attr("x", -20);
defs.append('pattern')
.attr("id", "cat")
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", "https://s-media-cache-ak0.pinimg.com/736x/92/9d/3d/929d3d9f76f406b5ac6020323d2d32dc.jpg")
.attr("width", 120)
.attr("height", 120)
.attr("x", -30)
.attr("y", -10);
var nodes = [{id:"foo"},{id:"bar"}, {id:"baz"},{id:"barbaz"}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(80))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2));
var links = svg.selectAll("foo")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var node = svg.selectAll("foo")
.data(nodes)
.enter()
.append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var nodeCircle = node.append("circle")
.attr("r", 30)
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", function(d,i){
return i%2 === 0 ? "url(#dog)" : "url(#cat)"
});
var texts = node.append("text")
.style("fill", "black")
.attr("dx", 36)
.attr("dy", 8)
.text(function(d) {
return d.id;
});
simulation.nodes(nodes);
simulation.force("link")
.links(edges);
simulation.on("tick", function() {
links.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
})
node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")
});
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 src="https://d3js.org/d3.v4.min.js"></script>
I am looking to animate this chart.
http://jsfiddle.net/NYEaX/1554/
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
I've animated the mask circle - looking to implement other animations/suggestions for the labels. If they tween like a pie chart, tween in an arc, fade in etc..
I did create a transition on the radius of the circle - kind of looked like the warner bros ending.
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(2000)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
How do I animate other features like the labels/pointers
I've managed to improve the inverse bubble chart with this code.
Where I have to set a fixed size for the circle first, mask it, then animate it - for the purpose of the labels.
function maskMaker(el){
var backcolor = $(el).data("color");
var backopacity = $(el).data("opacity");
var height = $(el).data("height");
var width = $(el).data("width");
var labelName = $(el).data("label-name");
var bubbleValue = $(el).data("bubble-value");
var displaceLeft = $(el).data("displace-left");
var displaceTop = $(el).data("displace-top");
var data = [{
"label": labelName,
"x": displaceLeft,
"y": displaceTop,
"value": bubbleValue
}];
console.log("MASK data", data);
// Set the main elements for the series chart
var svgroot = d3.select($(el)[0]).append("svg");
// filters go in defs element
var defs = svgroot.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "white")
.style("opacity", backopacity);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value-20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("opacity", 0.8);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
//apply the rest of the chart elements
var svg = svgroot
.attr("class", "series")
.attr("width", "1120px")
.attr("height", "500px")
.append("g")
.attr("transform", "translate(0,0)")
var rect = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor);
/*
//__labels
var centrallabel = svgroot.append("g")
.attr("class", "centrallabel")
.data(data);
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
*/
function addLabel(){
//__labels
var labels = svgroot.append("g")
.attr("class", "labels")
//__ enter
var labels = labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
//__ update
//labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y-10;
})
.text(function(d) {
return d.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
d.cx = 550;
d.cy = 250;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
//__ exit
labels.exit().remove();
//__labels
}
function addPointer(){
//__pointers
var pointers = svgroot.append("g")
.attr("class", "pointers");
var dots = defs.append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3);
var pointers = pointers.selectAll("path.pointer")
.data(data);
//__ enter
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.attr("marker-end", "url(#circ)")
.attr("mask", "url(#myMaskForPointer)")
//__ update
//pointers
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
//__ exit
pointers.exit().remove();
//__pointers
}
//delay for the mask
setTimeout(function(){
addLabel();
addPointer();
}, 1000);
}
Fade in could be implementing like :
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.style("opacity",0)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
.transition()
.duration(2000)
.style("opacity", 1)
As far as other animations you could start the pointer line off screen then transition it to it's endpoint. Or start with its length at 0 and transition it to full size. Transform/Translate would probably be useful - see (https://bl.ocks.org/mbostock/1345853)
I'm trying to create something that allows the user to scroll through an array of different links. The nodes stay the same, but the links change when the user clicks to see the next one.
However, it only shows one link in each array. Some arrays only have one link so that's okay, but the ones with more than one link need to be displayed correctly.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//var links = <?php echo json_encode(array_slice($data["links"], 0, 1)); ?>;
var links = <?php echo json_encode($links); ?>;
var nodes = <?php echo json_encode($nodes); ?>;
var count = <?php echo count($data); ?>;
var data_set = <?php echo json_encode($data); ?>;
//console.log(data_set);
//document.getElementById("demo").innerHTML = links;
max_count = links.length;
function draw()
{
function update_clique_view(direction)
{
geo_long_12_index += direction;
if(geo_long_12_index > max_count)
geo_long_12_index = 0;
else if(geo_long_12_index < 0)
geo_long_12_index = max_count - 1;
current_clique = links[geo_long_12_index];
d3.select("#link_number").remove();
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
console.log(current_clique);
link.data(current_clique);
force.links(current_clique);
force.start();
}
var width = 800,
height = 600;
geo_long_12_index = 0;
if(links.length > 0)
{
current_clique = links[geo_long_12_index];
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("rx", "20")
.attr("ry", "20")
.style("fill", "#000022")
.attr("class", "background");
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
// Navigation arrows
var leftarrow = svg.append("svg:image")
.attr("xlink:href", "/cgi-bin/images/left_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) - 25)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(-1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var rightarrow = svg.append("svg:image")
.attr("xlink:href","/cgi-bin/images/right_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) + 35)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var position_label = svg.append("text")
.attr("x", 10)
.attr("y", 30);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);
force
.nodes(nodes)
.links(current_clique)
.start();
var link = svg.selectAll(".link")
.data(current_clique)
.enter().append("line")
.style("stroke", function(d) { return d.colour; })
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return d.colour; })
.attr("class", "planet")
.on("mouseover", function(d) {d3.select(this).attr("r", 10);})
.on("mouseout", function(d) {d3.select(this).attr("r", 5);});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
}
draw();
</script>
var links = [[{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":3,"target":21,"orb":1.0416277777778,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":0,"target":17,"orb":0.71342777777778,"colour":"grey"},{"source":0,"target":22,"orb":0.77173888888888,"colour":"grey"},{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":11,"target":14,"orb":-1.6950111111111,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":4,"target":7,"orb":-0.53862777777772,"colour":"grey"},{"source":4,"target":8,"orb":-0.12282777777776,"colour":"grey"},{"source":4,"target":12,"orb":-0.5860555555555,"colour":"grey"},{"source":4,"target":15,"orb":0.11669444444448,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":13,"target":19,"orb":2.8450944444444,"colour":"grey"}],[{"source":11,"target":20,"orb":-2.9160166666667,"colour":"grey"},{"source":11,"target":21,"orb":-2.7081166666667,"colour":"grey"},{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":3,"target":9,"orb":-2.1064388888889,"colour":"grey"},{"source":3,"target":20,"orb":0.83372777777777,"colour":"grey"},{"source":9,"target":20,"orb":-2.9401666666667,"colour":"grey"}],[{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":5,"orb":-2.4144611111111,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":5,"target":6,"orb":-1.0340888888889,"colour":"grey"},{"source":5,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":5,"target":19,"orb":-0.51699444444445,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":6,"target":19,"orb":-0.51709444444444,"colour":"grey"},{"source":18,"target":19,"orb":2.1738666666667,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":6,"orb":-2.6075,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":6,"target":13,"orb":2.328,"colour":"grey"},{"source":6,"target":16,"orb":2.7100277777778,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":9,"orb":1.7401333333333,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}]];
var nodes = [{"name":"EARTHSUN","colour":"darkgreen"},{"name":"MERCURY","colour":"cyan"},{"name":"VENUS","colour":"magenta"},{"name":"MARS","colour":"red"},{"name":"JUPITER","colour":"gold"},{"name":"SATURN","colour":"darkslategray"},{"name":"URANUS","colour":"blue"},{"name":"NEPTUNE","colour":"blueviolet"},{"name":"PLUTO","colour":"maroon"},{"name":"CHIRON","colour":"orange"},{"name":"CERES","colour":"sandybrown"},{"name":"JUNO","colour":"orchid"},{"name":"PALLAS","colour":"royalblue"},{"name":"VESTA","colour":"pink"},{"name":"SAPPHO","colour":"plum"},{"name":"SEDNA","colour":"slategray"},{"name":"MARSSATURN","colour":null},{"name":"JUPITERSATURN","colour":"brown"},{"name":"SATURNCHIRON","colour":"indianred"},{"name":"SATURNURANUS","colour":null},{"name":"SATURNNEPTUNE","colour":null},{"name":"SATURNPLUTO","colour":null},{"name":"SATURNSEDNA","colour":null}];
Thanks for looking at this.
I have a problem with link.exit().remove(); and node.exit().remove();. If I set it in the initializeGraph method then I get several errors with the tick function I think. So my question is how or why do I get those errors:
Uncaught TypeError: undefined is not a function graph-d3.js:156initializeGraph graph-d3.js:156updateForceUsingNewNodes graph-d3.js:108createGraph graph-d3.js:18$.ajax.success ajax-stuff.js:106j jquery-2.1.1.min.js:2k.fireWith jquery-2.1.1.min.js:2x jquery-2.1.1.min.js:4n.prop.on.c jquery-2.1.1.min.js:4n.event.dispatch jquery-2.1.1.min.js:3r.handle jquery-2.1.1.min.js:3
3Error: Invalid value for <line> attribute x1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute x2="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y2="NaN" d3.min.js:1
Uncaught TypeError: Cannot read property 'attr' of undefined
Here is an exerpt of the source code. Not important lines are removed:
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 200;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
//here are some methods forming the json and array...
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
}
//here are some functions tick, mousedown and so on...
Well as you can see the svg.remove(); from the code is not needed. But until the exit().remove() do not work, it's needed. So yeah how to handle that with the tickevent/ .exit().remove().
Thank you for any tips.
PS: I used this very basic one https://gist.github.com/mbostock/1095795
and another which is very close to mine D3.js - exit() section does not remove all data
and this working not working, too Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
Whole code or let's say how I think it should run, but currently getting some errors (not identical to the code above, but just changed some lines)
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 400;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
.scaleExtent([0.4, 5]);
var drag = force.drag();
function createGraph(newJSON){
if (alreadyThere){
//svg.remove();
nodeCircles = {};
}
updateForceUsingNewNodes(generateObjects(newJSON));
alreadyThere = true;
currentJSON = newJSON;
force.start();
}
function updateGraph(newJSON){
//svg.remove();
findDuplicatesAndSetEmpty(newJSON);
deleteEmptyObjectsInJSON(newJSON);
currentJSON = currentJSON.concat(newJSON);
updateForceUsingNewNodes(generateObjects(currentJSON));
force.start();
}
function findDuplicatesAndSetEmpty(newJSON){
for (var i = 0; i < currentJSON.length; i++) {
for (var o = 0; o < newJSON.length; o++) {
if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target)
|| (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)){
newJSON[o] = {};
}
}
}
}
function deleteEmptyObjectsInJSON(json){
for (var i = 0; i < json.length; i++) {
var y = json[i].source;
if (y==="null" || y===null || y==="" || typeof y === "undefined"){
json.splice(i,1);
i--;
}
}
}
function updateGraphByRemoveElement(clickedNode, index){
svg.remove();
var json4Splicing = currentJSON;
for (var i = 0; i < json4Splicing.length; i++) {
if (json4Splicing[i].source.ID == clickedNode.ID){
json4Splicing[i] = {};
} else if (json4Splicing[i].target.ID == clickedNode.ID){
json4Splicing[i] = {};
}
}
deleteEmptyObjectsInJSON(json4Splicing);
deleteNode(force.nodes(),clickedNode);
currentJSON = json4Splicing;
updateForceRemoveElement(generateObjects(currentJSON));
}
function deleteNode(allNodes, clickedNode){
allNodes.forEach(function(node) {
if (node == clickedNode){
force.links().forEach(function(link) {
if (node.ID == link.source.ID){
link.target.linkCount--;
}
if (node.ID == link.target.ID){
link.source.linkCount--;
}
});
node.linkCount = 0;
}
});
}
function generateObjects(json) {
json.forEach(function(link) {
if (typeof(link.source) == "string"){
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass, linkCount:0});
link.source.linkCount++;
}
if (typeof(link.target) == "string"){
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass, linkCount:0});
link.target.linkCount++;
}
});
return json;
}
function updateForceRemoveElement(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function updateForceUsingNewNodes(links){
force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
force.links(d3.values(links));
initializeGraph();
}
function initializeGraph(){
zoom.on("zoom", zoomed);
drag.on("dragstart", dragstart);
force
.size([width, height])
.gravity(.1)
.charge(-400)
.friction(0.9)
.theta(0.9)
.linkStrength(0.9)
.distance(55)
.alpha(0.1)
.on("tick", tick);
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom).on("dblclick.zoom", null);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 32)
.attr("refY", -0.05)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#359AF4');
container = svg.append("g");
link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
link.exit().remove();
node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) { click(d); })
.on("dblclick", function(d) { dblclick(d); })
.on('contextmenu', function(data, index) {
d3.event.preventDefault();
updateGraphByRemoveElement(data, index);
})
.call(drag);
node
.append("circle")
.attr("r", 20)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", '#eee')
.style("stroke", '#fff')
.style("stroke-width", '0.5px');
node
.append("image")
.attr("xlink:href", function(d) {
if (d.class == "Person") {
return "pics/node_person.svg";
} else {
return "pics/node_appln.svg";
}} )
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40)
.attr("color", "white");
node
.append("text")
.attr("x", 20)
.attr("y", 4)
.style("fill", "#bbb")
.text(function(d) { return d.name; });
node
.append("circle")
.attr("r", 7)
.attr("cx", 0)
.attr("cy", -16)
.style("fill", '#359AF4');
node
.append("text")
.attr("text-anchor", "center")
.attr("x", -3)
.attr("y", -13)
.style("fill", "white")
.text(function(d) { return d.linkCount; });
node.exit().remove();
}
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function zoomed() {
d3.event.sourceEvent.stopPropagation();
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstart(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("fixed", d.fixed = true);
}
function mouseover() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","15px")
.style("fill","black");
d3.select(this).moveToFront();
}
function mouseout() {
d3.select(this).select("text").transition()
.duration(750)
.style("font-size","10px")
.style("fill","#ccc");
}
function click(d) {
$(boxIDName).empty();
if (d.class == "Person"){
$(boxIDName).append("<h2>Person/Company</h2>");
getNodeInfoPerson(d.ID);
}
if (d.class == "Appln"){
$(boxIDName).append("<h2>Application</h2>");
getNodeInfoAppln(d.ID);
}
}
function dblclick(d) {
entryClicked(d.ID+"|"+d.class,false);
}
I'm looking for a spider chart/radar chart for HTML/javascript that is also interactive. I would like the user to move all the endpoints and store the end result.
I have been searching for a while and although I have found some nice chart components all of them where static and could only updated using code.
Take a look at this.
This is what can be achieved with this alangrafu's code:
The example is really not interactive in the sense that you described, but it is interactive in other ways, and nothing stops you to implement the interactivity you desire, having the code from example as a good starting point.
An Interactive D3 Radar chart example:
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Radar chart</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="RadarChart.js"></script>
<style>
body {
overflow: hidden;
margin: 0;
font-size: 14px;
font-family: "Helvetica Neue", Helvetica;
}
#chart {
position: absolute;
top: 50px;
left: 100px;
}
</style>
</head>
<body>
<div id="body">
<div id="chart"></div>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
RadarChart.js
var RadarChart = {
draw: function(id, d, options){
var cfg = {
radius: 5,
w: 600,
h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
ToRight: 5,
TranslateX: 80,
TranslateY: 30,
ExtraWidthX: 100,
ExtraWidthY: 100,
color: d3.scale.category10()
};
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){
cfg[i] = options[i];
}
}
}
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, function(i){return d3.max(i.map(function(o){return o.value;}))}));
var allAxis = (d[0].map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
var Format = d3.format('%');
d3.select(id).select("svg").remove();
var g = d3.select(id)
.append("svg")
.attr("width", cfg.w+cfg.ExtraWidthX)
.attr("height", cfg.h+cfg.ExtraWidthY)
.append("g")
.attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")");
;
var tooltip;
//Circular segments
for(var j=0; j<cfg.levels-1; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels")
.data(allAxis)
.enter()
.append("svg:line")
.attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));})
.attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));})
.attr("class", "line")
.style("stroke", "grey")
.style("stroke-opacity", "0.75")
.style("stroke-width", "0.3px")
.attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");
}
//Text indicating at what % each level is
for(var j=0; j<cfg.levels; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels")
.data([1]) //dummy data
.enter()
.append("svg:text")
.attr("x", function(d){return levelFactor*(1-cfg.factor*Math.sin(0));})
.attr("y", function(d){return levelFactor*(1-cfg.factor*Math.cos(0));})
.attr("class", "legend")
.style("font-family", "sans-serif")
.style("font-size", "10px")
.attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")")
.attr("fill", "#737373")
.text(Format((j+1)*cfg.maxValue/cfg.levels));
}
series = 0;
var axis = g.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
axis.append("line")
.attr("x1", cfg.w/2)
.attr("y1", cfg.h/2)
.attr("x2", function(d, i){return cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y2", function(d, i){return cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("class", "line")
.style("stroke", "grey")
.style("stroke-width", "1px");
axis.append("text")
.attr("class", "legend")
.text(function(d){return d})
.style("font-family", "sans-serif")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "1.5em")
.attr("transform", function(d, i){return "translate(0, -10)"})
.attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-60*Math.sin(i*cfg.radians/total);})
.attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))-20*Math.cos(i*cfg.radians/total);});
d.forEach(function(y, x){
dataValues = [];
g.selectAll(".nodes")
.data(y, function(j, i){
dataValues.push([
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
]);
});
dataValues.push(dataValues[0]);
g.selectAll(".area")
.data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie"+series)
.style("stroke-width", "2px")
.style("stroke", cfg.color(series))
.attr("points",function(d) {
var str="";
for(var pti=0;pti<d.length;pti++){
str=str+d[pti][0]+","+d[pti][2]+" ";
}
return str;
})
.style("fill", function(j, i){return cfg.color(series)})
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function (d){
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", 0.1);
g.selectAll(z)
.transition(200)
.style("fill-opacity", .7);
})
.on('mouseout', function(){
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", cfg.opacityArea);
});
series++;
});
series=0;
d.forEach(function(y, x){
g.selectAll(".nodes")
.data(y).enter()
.append("svg:circle")
.attr("class", "radar-chart-serie"+series)
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0)})
.attr("cx", function(j, i){
dataValues.push([
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
]);
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total));
})
.attr("cy", function(j, i){
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total));
})
.attr("data-id", function(j){return j.axis})
.style("fill", cfg.color(series)).style("fill-opacity", .9)
.on('mouseover', function (d){
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 5;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition(200)
.style('opacity', 1);
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", 0.1);
g.selectAll(z)
.transition(200)
.style("fill-opacity", .7);
})
.on('mouseout', function(){
tooltip
.transition(200)
.style('opacity', 0);
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", cfg.opacityArea);
})
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
series++;
});
//Tooltip
tooltip = g.append('text')
.style('opacity', 0)
.style('font-family', 'sans-serif')
.style('font-size', '13px');
}
};
Script.js
var w = 500,
h = 500;
var colorscale = d3.scale.category10();
//Legend titles
var LegendOptions = ['Smartphone','Tablet'];
//Data
var d = [
[
{axis:"Email",value:0.59},
{axis:"Social Networks",value:0.56},
{axis:"Internet Banking",value:0.42},
{axis:"News Sportsites",value:0.34},
{axis:"Search Engine",value:0.48},
{axis:"View Shopping sites",value:0.14},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.05},
{axis:"Stream Music",value:0.07},
{axis:"Online Gaming",value:0.12},
{axis:"Navigation",value:0.27},
{axis:"App connected to TV program",value:0.03},
{axis:"Offline Gaming",value:0.12},
{axis:"Photo Video",value:0.4},
{axis:"Reading",value:0.03},
{axis:"Listen Music",value:0.22},
{axis:"Watch TV",value:0.03},
{axis:"TV Movies Streaming",value:0.03},
{axis:"Listen Radio",value:0.07},
{axis:"Sending Money",value:0.18},
{axis:"Other",value:0.07},
{axis:"Use less Once week",value:0.08}
],[
{axis:"Email",value:0.48},
{axis:"Social Networks",value:0.41},
{axis:"Internet Banking",value:0.27},
{axis:"News Sportsites",value:0.28},
{axis:"Search Engine",value:0.46},
{axis:"View Shopping sites",value:0.29},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.14},
{axis:"Stream Music",value:0.05},
{axis:"Online Gaming",value:0.19},
{axis:"Navigation",value:0.14},
{axis:"App connected to TV program",value:0.06},
{axis:"Offline Gaming",value:0.24},
{axis:"Photo Video",value:0.17},
{axis:"Reading",value:0.15},
{axis:"Listen Music",value:0.12},
{axis:"Watch TV",value:0.1},
{axis:"TV Movies Streaming",value:0.14},
{axis:"Listen Radio",value:0.06},
{axis:"Sending Money",value:0.16},
{axis:"Other",value:0.07},
{axis:"Use less Once week",value:0.17}
]
];
//Options for the Radar chart, other than default
var mycfg = {
w: w,
h: h,
maxValue: 0.6,
levels: 6,
ExtraWidthX: 300
}
//Call function to draw the Radar chart
//Will expect that data is in %'s
RadarChart.draw("#chart", d, mycfg);
////////////////////////////////////////////
/////////// Initiate legend ////////////////
////////////////////////////////////////////
var svg = d3.select('#body')
.selectAll('svg')
.append('svg')
.attr("width", w+300)
.attr("height", h)
//Create the title for the legend
var text = svg.append("text")
.attr("class", "title")
.attr('transform', 'translate(90,0)')
.attr("x", w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text("What % of owners use a specific service in a week");
//Initiate Legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', 'translate(90,20)')
;
//Create colour squares
legend.selectAll('rect')
.data(LegendOptions)
.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){ return colorscale(i);})
;
//Create text next to squares
legend.selectAll('text')
.data(LegendOptions)
.enter()
.append("text")
.attr("x", w - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(function(d) { return d; })
;
Live Example: http://bl.ocks.org/nbremer/6506614
Still looking? Check out this project on github, I think that is exactly what you are looking for:
https://github.com/jmstriegel/jquery.spidergraph
Demo: http://www.jqueryrain.com/?jhRGLHlE
I have been looking for such a library on my own for a long time and came across your post. Did you find another solution as well?