--- a/build.sbt Thu Oct 08 15:18:33 2015 +0200
+++ b/build.sbt Thu Nov 05 11:59:22 2015 +0100
@@ -27,7 +27,9 @@
buildInfoPackage := "ngtags",
buildInfoOptions ++= Seq(BuildInfoOption.ToMap, BuildInfoOption.ToJson)
-).jvmSettings().jsSettings()
+).jvmSettings().jsSettings(
+ jsDependencies += ProvidedJS / "RadarChart.js"
+)
lazy val ngtagsJS = ngtags.js
lazy val ngtagsJVM = ngtags.jvm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/src/main/resources/RadarChart.js Thu Nov 05 11:59:22 2015 +0100
@@ -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');
+ }
+};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/src/main/scala/Nvd3Chart.scala Thu Nov 05 11:59:22 2015 +0100
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2015 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.nvd3
+
+import scala.scalajs.js
+import scala.scalajs.js.annotation.{JSExport, JSExportAll}
+import scala.scalajs.js.Dynamic.{global => g}
+import scala.scalajs.js.JSConverters._
+
+private[nvd3] abstract class Constructable[C <: Constructable[C]](
+ b: Map[String, Any]
+, cf: Map[String, Any] => C) {
+ protected def v[T](n: Symbol, v: T): C = cf(b + (n.name -> v))
+ def toJs = b.toJSDictionary
+}
+
+class Axis(b: Map[String, Any])
+ extends Constructable[Axis](b, m => new Axis(m)) {
+ def axisLabel(l: String) = v('axisLabel, l)
+ def showMaxMin(b: Boolean) = v('showMaxMin, b)
+ def staggerLabels(b: Boolean) = v('staggerLabels, b)
+ def labelDistance(d: Int) = v('axisLabelDistance, d)
+ def tickFormat(f: js.Function1[Double, js.Any]) = v('tickFormat, f)
+ def rotateLabels(deg: Int) = v('rotateLabels, deg)
+}
+
+class Margin(b: Map[String, Any])
+ extends Constructable[Margin](b, m => new Margin(m)) {
+ def top(i: Int) = v('top, i)
+ def bottom(i: Int) = v('bottom, i)
+ def left(i: Int) = v('left, i)
+ def right(i: Int) = v('right, i)
+}
+
+class Legend(b: Map[String, Any])
+ extends Constructable[Legend](b, m => new Legend(m)) {
+ def margin(m: Margin) = v('margin, m)
+}
+
+class Chart(b: Map[String, Any])
+ extends Constructable[Chart](b, m => new Chart(m)) {
+
+ def transitionDuration(ms: Int) = v('transitionDuration, ms)
+ def height(h: Int) = v('height, h)
+ def width(w: Int) = v('width, w)
+ def x[R](f: js.Function1[js.Dynamic, R]) = v('x, f)
+ def y[R](f: js.Function1[js.Dynamic, R]) = v('y, f)
+ def average(f: js.Function1[js.Dynamic, Any]) = v('average, f)
+ def forceY(min: Double, max: Double) = v('forceY, js.Array(min, max))
+ def color(colors: js.Array[String]) = v('color, colors)
+ def color(colors: js.Any) = v('color, colors)
+ def useInteractiveGuideline(b: Boolean) = v('useInteractiveGuideline, b)
+ def clipVoronoi(b: Boolean) = v('clipVoronoi, b)
+ def useVoronoi(b: Boolean) = v('useVoronoi, b)
+ def xAxis(a: Axis) = v('xAxis, a.toJs)
+ def yAxis(a: Axis) = v('yAxis, a.toJs)
+ def margin(m: Margin) = v('margin, m.toJs)
+ def reduceXTicks(b: Boolean) = v('reduceXTicks, b)
+ def showValues(b: Boolean) = v('showValues, b)
+ def showControls(b: Boolean) = v('showControls, b)
+ def showLabels(b: Boolean) = v('showLabels, b)
+ def showLegend(b: Boolean) = v('showLegend, b)
+ def staggerLabels(b: Boolean) = v('staggerLabels, b)
+ def valueFormat[F, T](f: js.Function1[F, T]) = v('valueFormat, f)
+ def donut(b: Boolean) = v('donut, b)
+ def legend(l: Legend) = v('legend, l)
+ def labelThreshold(t: Double) = v('labelThreshold, t)
+ def clipEdge(b: Boolean) = v('clipEdge, b)
+}
+
+object Chart {
+ private def as(t: Symbol) = new Chart(Map("type" -> t.name))
+
+ def bulletChart = as('bulletChart)
+ def cumulativeLineChart = as('cumulativeLineChart)
+ def discreteBarChart = as('discreteBarChart)
+ def pieChart = as('pieChart)
+ def historicalBarChart = as('historicalBarChart)
+ def multiBarChart = as('multiBarChart)
+ def multiBarHorizontalChart = as('multiBarHorizontalChart)
+ def stackedAreaChart = as('stackedAreaChart)
+
+ def axis(l: String) = new Axis(Map()) axisLabel(l)
+ def margin = new Margin(Map())
+ def legend = new Legend(Map())
+}
+
+@JSExportAll
+case class Options(var chart: js.Dictionary[Any])
+
+class RadarChartConfig(b: Map[String, Any])
+ extends Constructable[RadarChartConfig](b, m => new RadarChartConfig(m)) {
+
+ def radius(r: Int) = v('radius, r)
+ def height(h: Int) = v('h, h)
+ def width(w: Int) = v('w, w)
+ def factor(f: Double) = v('factor, f)
+ def factorLegend(f: Double) = v('factorLegend, f)
+ def levels(l: Int) = v('levels, l)
+ def maxValue(m: Double) = v('maxValue, m)
+ def opacityArea(o: Double) = v('opacityArea, o)
+ def toRight(r: Int) = v('ToRight, r)
+ def translateX(t: Int) = v('TranslateX, t)
+ def translateY(t: Int) = v('TranslateY, t)
+ def extraWidthX(w: Int) = v('ExtraWidthX, w)
+ def extraWidthY(w: Int) = v('ExtraWidthY, w)
+ def color(c: js.Function1[Int, String]) = v('color, c)
+ def offset(d: Double) = v('offset, d)
+ def formatValue(f: js.Function1[Double, String]) = v('formatValue, f)
+ def formatAxis(f: js.Function1[Double, String]) = v('formatAxis, f)
+ def drawMaxLevel(b: Boolean) = v('drawMaxLevel, b)
+}
+
+object RadarChartConfig {
+ def default = new RadarChartConfig(Map())
+}
+
+object RadarChart {
+ case class V(val n: String, val v: Double, val t: Option[() => String] = None) {
+ @JSExport
+ def axis = n
+ @JSExport
+ def value = v
+ @JSExport
+ def tooltip() = t.map(_()).getOrElse("")
+ }
+
+ def apply(id: String, data: Seq[Seq[V]], cfg: RadarChartConfig) =
+ g.RadarChart.draw(id, data.map(_.toJSArray).toJSArray, cfg.toJs)
+}
+
+object MultiBarChart {
+ case class V[X](_x: X, _y: Double) {
+ @JSExport
+ def x = _x
+ @JSExport
+ def y = _y
+ }
+
+ case class Series[X](_key: String, _values: Seq[V[X]]) {
+ @JSExport
+ def key = _key
+ @JSExport
+ def values = _values.toJSArray
+ }
+}
+
+// vim: set ts=2 sw=2 et: