Skip to content

Instantly share code, notes, and snippets.

@Thanaporn-sk
Created November 1, 2019 14:32
Show Gist options
  • Save Thanaporn-sk/087cfc8b0bbd4c916e8f7053567e2380 to your computer and use it in GitHub Desktop.
Save Thanaporn-sk/087cfc8b0bbd4c916e8f7053567e2380 to your computer and use it in GitHub Desktop.
Nodes on Radar Chart 3
license: CC0-1.0

this version tries out radarChart.js with a new dataset and makes some improvements.

+ parameter-ize the axisName and value fields:

var radarChartOptions = {
  w: width,
  h: height,
  margin: margin,
  maxValue: 1,
  wrapWidth: 135,
  levels: 5,
  roundStrokes: true,
  color: color,
  axisName: "statement",
  value: "percentCorrect"
};

+ subtract Math.PI/2 from angleSlice*i when drawing the radial axis lines so that they line up with the axis label text and the points (it seems this is only a problem your dataset has a number of elements that is not divisible by four)

+ sort the data for the areas from largest to smallest by average value (an approximation of actual blob area) so that that the smallest area is drawn last and therefore appears on top

//Calculate the average value for each area
data.forEach(function(d){
	d[value + "Average"] = d3.mean(d.values, function(e){ return e[value] }); 
})

//Sort
data = data.sort(function(a, b){
	var a = a[value + "Average"],
			b = b[value + "Average"];
	return b - a;
  })

an iteration on the bl.ock radar chart for nested data by @micahstubbs

and a further iteration on the bl.ock Radar Chart Redesign created by @nbremer

the data is a subset of table 7-8: Correct answers to factual knowledge questions in physical and biological sciences, by country/region: Most recent year from the US National Science Foundation

forked from micahstubbs's block: radar chart with smallest area on top

forked from newsummit's block: Nodes on Radar Chart

forked from newsummit's block: Nodes on Radar Chart

forked from newsummit's block: Nodes on Radar Chart 3

{
"nodes": [
{"id": "001", "name": "title", "score": 0.01},
{"id": "002", "name": "title", "score": 0.8},
{"id": "003", "name": "title", "score": 0.4},
{"id": "004", "name": "title", "score": 0.95},
{"id": "005", "name": "title", "score": 0.33},
{"id": "006", "name": "title", "score": 0.25},
{"id": "007", "name": "title", "score": 0.11},
{"id": "008", "name": "title", "score": 0.05},
{"id": "009", "name": "title", "score": 0.08},
{"id": "010", "name": "title", "score": 0.77}
]
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/ >
<title>Smoothed D3.js Radar Chart</title>
<!-- Google fonts -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<!-- D3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
font-family: 'Open Sans', sans-serif;
font-size: 11px;
font-weight: 300;
fill: #242424;
text-align: center;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
cursor: default;
}
.legend {
font-family: 'Raleway', sans-serif;
fill: #333333;
}
.tooltip {
fill: #333333;
}
</style>
</head>
<body>
<div class="radarChart"></div>
<script src="radarChart.js"></script>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var margin = {top: 50, right: 50, bottom: 50, left: 50},
width = Math.min(900, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
//////////////////////////////////////////////////////////////
//////////////////// Draw the Chart //////////////////////////
//////////////////////////////////////////////////////////////
/* var color = d3.scale.ordinal()
.range(["#EDC951","#CC333F","#00A0B0"]); */
var color = d3.scaleBand().range(["#EDC951","#CC333F","#00A0B0"]);
var radarChartOptions = {
w: width,
h: height,
margin: margin,
sourceNode: '001',
maxValue: 1,
wrapWidth: 135,
levels: 5,
roundStrokes: true,
color: color,
axisName: "statement",
value: "percentCorrect"
};
//Load the data and Call function to draw the Radar chart
d3.json("data.json", function(error, data){
RadarChart(".radarChart", data, radarChartOptions);
});
</script>
</body>
</html>
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/////////////// Written by Nadieh Bremer ////////////////
////////////////// VisualCinnamon.com ///////////////////
/////////// Inspired by the code of alangrafu ///////////
/////////////////////////////////////////////////////////
function RadarChart(id, data, options) {
var cfg = {
w: 800, //Width of the circle
h: 800, //Height of the circle
margin: {top: 20, right: 20, bottom: 20, left: 20}, //The margins around the circle
levels: 4, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.05, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleOrdinal(d3.schemeCategory10), //Color function
axisName: "axis",
value: "value",
sortAreas: true,
};
//Put all of the options into a variable called cfg
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){ cfg[i] = options[i]; }
}
}
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
var maxValue = Math.max(cfg.maxValue,
d3.max(data.nodes.map(function(o) {
return o.score;
}))
);
var radius = Math.min(cfg.w/2, cfg.h/2), //Radius of the outermost circle
Format = d3.format('%'); //Percentage formatting
//Scale for the radius
var rScale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue]);
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var svg = d3.select(id).append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar"+id);
//Append a g element
var g = svg.append("g")
.attr("transform", "translate(" + (cfg.w/2 + cfg.margin.left) + "," + (cfg.h/2 + cfg.margin.top) + ")");
/////////////////////////////////////////////////////////
////////// Glow filter for some extra pizzazz ///////////
/////////////////////////////////////////////////////////
//Filter for the outside glow
var filter = g.append('defs').append('filter').attr('id','glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'),
feMerge = filter.append('feMerge'),
feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'),
feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic');
/////////////////////////////////////////////////////////
/////////////// Draw the Circular grid //////////////////
/////////////////////////////////////////////////////////
//Wrapper for the grid & axes
var axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid.selectAll(".levels")
.data(d3.range(1,(cfg.levels+1)).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function(d, i){return radius/cfg.levels*d;})
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter" , "url(#glow)");
//Text indicating at what % each level is
axisGrid.selectAll(".axisLabel")
.data(d3.range(1,(cfg.levels+1)).reverse())
.enter().append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", function(d){return -d*radius/cfg.levels;})
.attr("dy", "0.4em")
.style("font-size", "14px")
.attr("fill", "#737373")
.text(function(d,i) {
//console.log('d: '+ d + ' levels : ' + cfg.levels);
return Format(maxValue * d/cfg.levels);
});
/////////////////////////////////////////////////////////
/////////////// Draw the Circles ////////////////////////
/////////////////////////////////////////////////////////
var simulation = d3.forceSimulation()
.force("link", d3.forceLink()
.distance(function(d){
if(Number.isInteger(d.source.score)) {
dist = d.value; Math.random;
}
else {
dist = 30;
}
//console.log(d);
return dist;
})
.strength(0.5))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(cfg.w/2 + cfg.margin.left, cfg.h/2 + cfg.margin.top));
var nodes = data.nodes,
nodeById = d3.map(nodes, function(d) { return d.id; }),
links = [],
bilinks = [];
nodes.forEach(function(nx) {
var s = nodeById.get(nx.id),
t = nodeById.get(cfg.sourceNode),
i = {}; // intermediate node
nodes.push(i);
links.push({source: s, target: i, score: 0}, {source: i, target: t});
bilinks.push([s, i, t]);
});
console.log('links: ' ,links);
console.log('nodes: ' ,nodes);
var link = svg.selectAll(".link")
.data(bilinks)
.enter().append("path")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes.filter(function(d) { if(d.id) { return d;} }))
.enter().append("circle")
.attr("class", "node")
.attr("r", 15)
.attr("fill", function(d) { return color(d.group); });
/*.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));*/
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
link.attr("d", positionLink);
node.attr("transform", positionNode);
}
function positionLink(d) {
return "M" + d[0].x + "," + d[0].y
+ "S" + d[1].x + "," + d[1].y
+ " " + d[2].x + "," + d[2].y;
}
function positionNode(d) {
//console.log(d);
return "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;
}
/////////////////////////////////////////////////////////
/////////////////// Helper Function /////////////////////
/////////////////////////////////////////////////////////
//Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}//wrap
}//RadarChart
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment