Modularized RadarChart.
authorTomas Zeman <tzeman@volny.cz>
Thu, 31 Mar 2016 15:56:00 +0200
changeset 29 147153d57133
parent 28 caab42578564
child 30 480a543a4c03
Modularized RadarChart.
build.sbt
js/src/main/resources/RadarChart.js
radar-chart/js/src/main/resources/RadarChart.js
--- a/build.sbt	Thu Mar 31 15:02:45 2016 +0200
+++ b/build.sbt	Thu Mar 31 15:56:00 2016 +0200
@@ -1,18 +1,23 @@
+import sbt.Project.project
+
 lazy val root = project.in(file(".")).
-  aggregate(ngtagsJS, ngtagsJVM).
+  aggregate(ngtagsJS, ngtagsJVM, radarChart).
   settings(
     publish := {},
     publishLocal := {}
   )
 
-lazy val ngtags = crossProject.in(file(".")).
-enablePlugins(BuildInfoPlugin).
-settings(
+lazy val buildSettings = Seq(
   organization := "net.tz",
   name := "ngTags",
   scalaVersion := "2.11.8",
-  licenses += ("Apache-2.0", url("http://opensource.org/licenses/Apache-2.0")),
+  licenses += ("Apache-2.0", url("http://opensource.org/licenses/Apache-2.0"))
+)
 
+lazy val ngtags = crossProject.in(file(".")).
+enablePlugins(BuildInfoPlugin).
+settings(buildSettings:_*).
+settings(
   libraryDependencies ++= Seq(
     "com.lihaoyi" %%% "scalatags" % "0.5.3"
     //"com.github.japgolly.scalacss" %%% "ext-scalatags" % "0.1.0"
@@ -27,11 +32,17 @@
   buildInfoPackage := "ngtags",
   buildInfoOptions ++= Seq(BuildInfoOption.ToMap, BuildInfoOption.ToJson)
 
-).jvmSettings().jsSettings(
-  jsDependencies += ProvidedJS / "RadarChart.js"
-)
+).jvmSettings().jsSettings()
 
 lazy val ngtagsJS = ngtags.js
 lazy val ngtagsJVM = ngtags.jvm
 
+lazy val radarChart = project.in(file("radar-chart")).
+  enablePlugins(ScalaJSPlugin).
+  settings(buildSettings: _*).
+  settings(moduleName := "ngtags-radarchart").
+  settings(
+    jsDependencies += ProvidedJS / "RadarChart.js"
+  )
+
 // vim: et ts=2 sw=2 syn=scala 
--- a/js/src/main/resources/RadarChart.js	Thu Mar 31 15:02:45 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-/*
- * Cloned from https://gist.github.com/nbremer/6506614
- */
-
-//Practically all this code comes from https://github.com/alangrafu/radar-chart-d3
-//I only made some additions and aesthetic adjustments to make the chart look better 
-//(of course, that is only my point of view)
-//Such as a better placement of the titles at each line end, 
-//adding numbers that reflect what each circular level stands for
-//Not placing the last level and slight differences in color
-//
-//For a bit of extra information check the blog about it:
-//http://nbremer.blogspot.nl/2013/09/making-d3-radar-chart-look-bit-better.html
-
-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(),
-	 offset: 0.0,
-	 formatAxis: d3.format('%'),
-	 formatValue: d3.format('%'),
-	 drawMaxLevel: false
-	};
-
-	if (d.length == 0)
-	  return;
-
-	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;}))})) + cfg.offset;
-	var allAxis = (d[0].map(function(i, j){return i}));
-	var total = allAxis.length;
-	var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
-	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
-	var maxLevel = cfg.drawMaxLevel ? cfg.levels : cfg.levels - 1;
-	for(var j=0; j<maxLevel; 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", "level level-" + j + " level-val-" +
-	     cfg.formatAxis((j+1)*cfg.maxValue/cfg.levels - cfg.offset))
-	   .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")
-	   .attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")")
-	   .text(cfg.formatAxis((j+1)*cfg.maxValue/cfg.levels - cfg.offset));
-	}
-	
-	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.axis})
-		.style("font-family", "sans-serif")
-		.style("font-size", "11px")
-		.attr("text-anchor", "middle")
-		.attr("dy", "1em")
-		.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);})
-		.on('mouseover', function(d) {
-	          tooltip.text('');
-		  var a = d.tooltip().split("\n");
-		  for (var i = 0; i < a.length; i++) {
-		    tooltip
-		      .attr('x', 0)
-		      .attr('y', 0)
-		      .style('opacity', 1)
-		      .append('tspan')
-		      .attr('x', 0)
-		      .attr('y', cfg.h + cfg.ExtraWidthY - cfg.TranslateY + 12*(i-a.length)+9)
-		      .text(a[i])
-                      .transition(100);
-		  }
-		})
-		.on('mouseout', function(d) {
-		  tooltip.transition(100).style('opacity', 0);
-		});
-
- 
-	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 + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
-			cfg.h/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 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][1]+" ";
-						 }
-						 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 cfg.formatValue(j.value)})
-		.attr("cx", function(j, i){
-		  dataValues.push([
-			cfg.w/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
-			cfg.h/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
-		]);
-		return cfg.w/2*(1-(Math.max(j.value + cfg.offset, 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 + cfg.offset, 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(cfg.formatValue(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 cfg.formatValue(j.value)});
-
-	  series++;
-	});
-	//Tooltip
-	tooltip = g.append('text')
-			   .style('opacity', 0)
-			   .style('font-family', 'sans-serif')
-			   .style('font-size', '13px');
-  }
-};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/radar-chart/js/src/main/resources/RadarChart.js	Thu Mar 31 15:56:00 2016 +0200
@@ -0,0 +1,245 @@
+/*
+ * Cloned from https://gist.github.com/nbremer/6506614
+ */
+
+//Practically all this code comes from https://github.com/alangrafu/radar-chart-d3
+//I only made some additions and aesthetic adjustments to make the chart look better 
+//(of course, that is only my point of view)
+//Such as a better placement of the titles at each line end, 
+//adding numbers that reflect what each circular level stands for
+//Not placing the last level and slight differences in color
+//
+//For a bit of extra information check the blog about it:
+//http://nbremer.blogspot.nl/2013/09/making-d3-radar-chart-look-bit-better.html
+
+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(),
+	 offset: 0.0,
+	 formatAxis: d3.format('%'),
+	 formatValue: d3.format('%'),
+	 drawMaxLevel: false
+	};
+
+	if (d.length == 0)
+	  return;
+
+	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;}))})) + cfg.offset;
+	var allAxis = (d[0].map(function(i, j){return i}));
+	var total = allAxis.length;
+	var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
+	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
+	var maxLevel = cfg.drawMaxLevel ? cfg.levels : cfg.levels - 1;
+	for(var j=0; j<maxLevel; 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", "level level-" + j + " level-val-" +
+	     cfg.formatAxis((j+1)*cfg.maxValue/cfg.levels - cfg.offset))
+	   .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")
+	   .attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")")
+	   .text(cfg.formatAxis((j+1)*cfg.maxValue/cfg.levels - cfg.offset));
+	}
+	
+	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.axis})
+		.style("font-family", "sans-serif")
+		.style("font-size", "11px")
+		.attr("text-anchor", "middle")
+		.attr("dy", "1em")
+		.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);})
+		.on('mouseover', function(d) {
+	          tooltip.text('');
+		  var a = d.tooltip().split("\n");
+		  for (var i = 0; i < a.length; i++) {
+		    tooltip
+		      .attr('x', 0)
+		      .attr('y', 0)
+		      .style('opacity', 1)
+		      .append('tspan')
+		      .attr('x', 0)
+		      .attr('y', cfg.h + cfg.ExtraWidthY - cfg.TranslateY + 12*(i-a.length)+9)
+		      .text(a[i])
+                      .transition(100);
+		  }
+		})
+		.on('mouseout', function(d) {
+		  tooltip.transition(100).style('opacity', 0);
+		});
+
+ 
+	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 + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
+			cfg.h/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 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][1]+" ";
+						 }
+						 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 cfg.formatValue(j.value)})
+		.attr("cx", function(j, i){
+		  dataValues.push([
+			cfg.w/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), 
+			cfg.h/2*(1-(parseFloat(Math.max(j.value + cfg.offset, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
+		]);
+		return cfg.w/2*(1-(Math.max(j.value + cfg.offset, 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 + cfg.offset, 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(cfg.formatValue(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 cfg.formatValue(j.value)});
+
+	  series++;
+	});
+	//Tooltip
+	tooltip = g.append('text')
+			   .style('opacity', 0)
+			   .style('font-family', 'sans-serif')
+			   .style('font-size', '13px');
+  }
+};