Skip to content

Instantly share code, notes, and snippets.

@mph006
Last active March 8, 2016 01:49
Show Gist options
  • Save mph006/9e11bdbfcaed13c7a2b7 to your computer and use it in GitHub Desktop.
Save mph006/9e11bdbfcaed13c7a2b7 to your computer and use it in GitHub Desktop.
Radar Chart - Update Values

Basic Radar chart layout code can be found here : http://bl.ocks.org/nbremer/6506614

The purpose of this block is to explore the ability to update an elements value on a radar chart. This makes the radar chart both an input and visualization element.

var d = [
{
name:"Data Set",
color:"#d62728",
skills:[
{axis:"Node",value:1},
{axis:"D3",value:1},
{axis:"Javascript",value:2},
{axis:"HTML",value:3},
{axis:"CSS",value:3},
{axis:"Haskell",value:9},
{axis:"Java",value:10},
{axis:"C",value:8},
{axis:"C++",value:7},
{axis:"Julia",value:8},
{axis:"FORTRAN",value:3},
{axis:"ADA",value:1},
{axis:"MIPS",value:1},
{axis:"COBOL",value:3},
{axis:"SQL",value:4}
],
}
];
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Radar Chart - Update Polygon</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js" charset-"utf-8"></script>
<style type="text/css">
/* line 137, ../sass/radarChart.scss */
body {
overflow: hidden;
margin: 0;
font-size: 14px;
font-family: "proxima_nova_reg", sans-serif;
}
body .edit-btn {
position: absolute;
left: 100px;
top: 50px;
height: 20px;
background-color: #a8a8a8;
color: white;
border-radius: 40px;
padding: 8px 16px 7px 13px;
cursor: default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: 1;
visibility: visible;
}
/* line 271, ../sass/radarChart.scss */
#chart {
position: absolute;
left: 100px;
}
/* line 493, ../sass/radarChart.scss */
.yaxis, .xaxis {
font: 10px sans-serif;
width: 100px;
}
/* line 498, ../sass/radarChart.scss */
.yaxis path,
.yaxis line,
.yaxis tick {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
/* line 507, ../sass/radarChart.scss */
.xaxis path,
.xaxis line {
fill: none;
stroke: black;
stroke-dasharray: 10,10;
shape-rendering: crispEdges;
}
body .update-value {
position: absolute;
left: 505px;
top: 365px;
width: 65px;
height: 65px;
background-color: #a8a8a8;
color: white;
border-radius: 50%;
cursor: default;
opacity: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
z-index: 9;
}
</style>
</head>
<body>
<div id="body">
<div id="chart"></div>
<div class="edit-btn">Edit Data Set</div>
<div class="update-value"></div>
</div>
<script type="text/javascript" src="data.js"></script>
<script type="text/javascript" src="radarChart.js"></script>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
//Options for the Radar chart, other than default
var cfg = {
w: 675,
h: 675,
maxValue: 10,
levels: 10,
opacityArea: 0.20,
radius:5,
radians: 2 * Math.PI,
factor:1,
factorLegend: 0.85,
ToRight: 5,
TranslateX: 100,
TranslateY:60,
ExtraWidthX: 700,
ExtraWidthY: 200,
duration:200
};
//Will need this for the drag update, not sure how to pass it back and forth
var maxAxisValues = [];
function init(){
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, function(i){return d3.max(i.skills.map(function(o){return o.value;}));}));
var allAxis = (d[0].skills.map(function(i, j){return i.axis;}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
d3.select("#chart").select("svg").remove();
var g = d3.select("#chart")
.append("svg")
.attr("width", cfg.w+cfg.ExtraWidthX)
.attr("height", cfg.h+cfg.ExtraWidthY)
.append("g")
.attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")")
.attr("id","chartArea");
for(var j=0; j<cfg.levels; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
//Web
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", "web")
.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 labels along 12 0'clock axis [1-10]
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", "skill-value")
.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((j+1)*cfg.maxValue/cfg.levels);
}
series = 0;
var maxAxisValues = [];
function drawAxis(){
var axis = g.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
.attr("id",function(d){return d;});
//Axis lines
axis.append("line")
.attr("x1", cfg.w/2)
.attr("y1", cfg.h/2)
.attr("x2", function(j, i){
maxAxisValues[i] = {x:cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total)), y:0};
return maxAxisValues[i].x;
})
.attr("y2", function(j, i){
maxAxisValues[i].y = cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));
return maxAxisValues[i].y;
})
.attr("class", "line").style("stroke", "grey").style("stroke-width", "1px");
//Axis labels
axis.append("text")
.attr("class", "skill-legend")
.text(function(d){return d;})
.style("font-family", "sans-serif")
.style("font-size", "11px")
.style("cursor","pointer")
.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))-80*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);});
}
drawAxis();
initPolygon();
//Fill Areas
function initPolygon(){
d.forEach(function(y, x){
dataValues = [];
g.selectAll(".nodes")
.data(y.skills, 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-series"+series)
.attr("id","radar-chart-area-"+y.name.replace(" ","-"))
.style("stroke-width", "2px")
.style("stroke", y.color)
.attr("points",function(d) {
var str="";
for(var pti=0;pti<d.length;pti++){
str=str+d[pti][0]+","+d[pti][1]+" ";
}
return str;
})
.style("fill", function(j, i){return y.color;})
.style("fill-opacity", cfg.opacityArea);
series++;
});
}
series=0;
var drag = d3.behavior.drag()
.on("drag",move)
.on("dragend",dragend);
function dragend(){
d3.select(".updatevalue.skill")
.style("display","block")
.style("text-align","center")
.style("margin-top","13px")
.style("font-size","14px")
.transition().duration(500)
.text("Drag a Point to Edit");
d3.select(".updatevalue.value").style("visibility","hidden");
}
function move(dobj, i){
this.parentNode.appendChild(this);
var dragTarget = d3.select(this);
var oldData = dragTarget.data()[0];
var oldX = parseFloat(dragTarget.attr("cx")) - cfg.w/2;
var oldY = cfg.h/2 - parseFloat(dragTarget.attr("cy"));
//Bug for vector @ 270deg -Infinity/Infinity slope
oldX=(Math.abs(oldX)<0.0000001)?0:oldX;
oldY=(Math.abs(oldY)<0.0000001)?0:oldY;
var newY = 0, newX = 0, newValue = 0;
var maxX = maxAxisValues[i].x - cfg.w/2;
var maxY = cfg.h/2 - maxAxisValues[i].y;
if(oldX === 0) {
newY = oldY - d3.event.dy;
if(Math.abs(newY) > Math.abs(maxY)) {
newY = maxY;
}
newValue = ((newY/oldY) * oldData.value).toFixed(2);
}
else {
var slope = oldY / oldX;
newX = d3.event.dx + parseFloat(dragTarget.attr("cx")) - cfg.w/2;
if(Math.abs(newX) > Math.abs(maxX)) {
newX = maxX;
}
newY = newX * slope;
var ratio = newX / oldX;
newValue = (ratio * oldData.value).toFixed(2);
}
//Bound the drag behavior to the max and min of the axis, not by pixels but by value calc (easier)
if(newValue >=1 && newValue<= cfg.levels){
dragTarget
.attr("cx", function(){return newX + cfg.w/2 ;})
.attr("cy", function(){return cfg.h/2 - newY;});
//Updating the data set with the new value
(dragTarget.data()[0]).value = newValue;
//center display for value
d3.select(".updatevalue.skill").text((dragTarget.data()[0]).axis)
.style("display","block")
.style("font-size","12px")
.style("text-align","center")
.style("margin","20px 0 5px 0");
d3.select(".updatevalue.value").text(newValue)
.style("display","block")
.style("text-align","center")
.style("visibility","visible");
//reCalculatePoints();
//drawPoly();'
updatePoly();
}
//Release the drag listener on the node if you hit the min/max values
//https://github.com/mbostock/d3/wiki/Drag-Behavior
else{
if(newValue <=0){newValue =0;}
else if(newValue >=cfg.levels){newValue = cfg.levels;}
dragTarget.on("drag",null);
}
}
function updatePoly(){
d.forEach(function(y, x){
dataValues = [];
g.selectAll(".nodes")
.data(y.skills, 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 = [dataValues];
g.selectAll("#radar-chart-area-Data-Set")
.data(dataValues)
.attr("points",function(d) {
console.log(d);
var str="";
for(var pti=0;pti<d.length;pti++){
str=str+d[pti][0]+","+d[pti][1]+" ";
}
return str;
});
});
}
//Put circles on the polygon at inflection points
d.forEach(function(y, x){
g.selectAll(".nodes")
.data(y.skills).enter()
.append("svg:circle")
.attr("class", "radar-chart-series"+series)
.attr("id","radar-chart-points-"+y.name.replace(" ","-"))
.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", y.color)
.style("fill-opacity", 0.75)
.style("z-index",12)
.style("cursor","pointer")
.call(drag);
series++;
});
}//end init()
init();
function updateEditable(){
var group = $('.comparision-pool .avatar .name');
if(group.length ===1){
d3.select(".edit-btn").transition().duration(500).style("opacity",1).text("Edit "+group[0].innerText).style("visibility","visible");
}
else{
d3.select(".edit-btn").transition().duration(200).style("opacity",0).style("visibility","hidden");
//Update bar chart data with newly modified polygon value
}
}
function editSet(){
var button = d3.select(".edit-btn");
//Hard coded for now
var name = d[0].name;
var centerDisplay = d3.select(".update-value");
if(button[0][0].innerText.includes("Edit")){
button.text("Save Changes");
centerDisplay.append("g").attr("class","updatevalue skill")
.style("display","block")
.style("text-align","center")
.style("margin-top","13px")
.text("Drag a Point to Edit");
centerDisplay.append("g").attr("class","updatevalue value");
centerDisplay.transition().duration(500).style("opacity",1).style("visibility","visible");
// d3.select("#radar-chart-area-"+name.replace(" ","-")).transition().duration(500).style("opacity",0);
// d3.select("#radar-chart-area-"+name.replace(" ","-")).style("visibility","hidden");
d3.selectAll("#radar-chart-points-"+name.replace(" ","-"))
.transition().duration(750).attr("r",15);
d3.select(".point-value").style("visibility","hidden");
}
else if(button[0][0].innerText.includes("Save")){
button.text("Edit "+name);
d3.selectAll(".updatevalue").remove();
centerDisplay.transition().duration(500).style("opacity",0).style("visibility","hidden");
// init();
// d3.select('#radar-chart-area-'+name.replace(" ","-"))
// .style("opacity",0);
// d3.selectAll('#radar-chart-points-'+name.replace(" ","-"))
// .style("opacity",0);
//http://stackoverflow.com/questions/13136355/d3-js-remove-force-drag-from-a-selection
d3.selectAll("#radar-chart-points-"+name.replace(" ","-"))
.transition().duration(750).attr("r",5);
// d3.select('#radar-chart-area-'+name.replace(" ","-"))
// .transition().duration(750).style("opacity",1);
// d3.selectAll('#radar-chart-points-'+name.replace(" ","-"))
// .transition().duration(750).style("opacity",1);
}
}
d3.select(".edit-btn").on("click",editSet);
@mph006
Copy link
Author

mph006 commented Jul 13, 2015

This may be helpful to update the polygon on drag http://bl.ocks.org/mbostock/4342190

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment