Modularized RadarChart.
--- 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');
+ }
+};