Skip to content

Instantly share code, notes, and snippets.

@chrisrzhou
Last active December 1, 2021 09:57
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save chrisrzhou/2421ac6541b68c1680f8 to your computer and use it in GitHub Desktop.
Save chrisrzhou/2421ac6541b68c1680f8 to your computer and use it in GitHub Desktop.
D3 Radar Chart

D3 Radar Chart

bl.ocks.org link

A radar chart visualizes multivariate data in a 2D chart of three or more quantitative variables represented on axes.

The project is created using AngularJS and D3.js.


Description

  • This is a variation of the original and improved D3 radar chart.

  • Major improvements include:

    • Refactoring D3 components (levels, labels, axes, polygons, legend), which now can be controlled through the config object.
    • Abstracting the building and rendering portions of the D3 visualization.
    • Aside from the basic stacked view, this variation includes a facetting option to plot the graphs in a facet grid.
  • Use the configuration parameters to adjust the plot to your tastes, and you can also choose to view the plots stacked vs facetted.

  • The data input has to be a 4-column CSV (headers MUST be included) conforming to the data schema of:

  • group (int/string): data to be grouped into an object to plot the required polygon on the radar chart.

  • axis (int/string): the axis of the radar charts (dimensions of the multivariate data).

  • value (int): the data value of the given record.

  • description (int/string): not a mandatory field, and additional columns after this are accepted as well.

  • All D3 logic is contained in the radar.js file. You should be able to look at just this file if you intend to use the visualization logic without AngularJS.


Files

  • index.html: Main HTML file.

  • app.js: AngularJS core logic to connect Javascript components and D3 visualization updates with user interactions. The directive onReadFile handles file uploads and the directive radar draws the D3 visualization.

  • radar.js: All D3 logic is contained in this file. You should use this file if you are looking solely to use D3 without AngularJS.

  • radarDraw.js: This is the directive-link function called by the AngularJS directive radar in app.js. Funnels the dataset from the angular app into the D3 drawing logic called from radar.js.

  • style.css: stylesheet containing optional D3 classes that can be adjusted (commented out)

  • data.csv: Three CSV-data files for sample downloads and uploads to the app.


Other Notes

  • You may notice Angular digest errors in the console due to $scope.$watch. Not too familiar on resolving these issues.
  • A big help from this fiddle to help implement an AngularJS FileReader.

Analytics

(function() {
angular.module("RadarChart", [])
.directive("radar", radar)
.directive("onReadFile", onReadFile)
.controller("MainCtrl", MainCtrl);
// controller function MainCtrl
function MainCtrl($http) {
var ctrl = this;
init();
// function init
function init() {
// initialize controller variables
ctrl.examples = [
"data_the_avengers",
"data_plant_seasons",
"data_car_ratings"
];
ctrl.exampleSelected = ctrl.examples[0];
ctrl.getData = getData;
ctrl.selectExample = selectExample;
// initialize controller functions
ctrl.selectExample(ctrl.exampleSelected);
ctrl.config = {
w: 250,
h: 250,
facet: false,
levels: 5,
levelScale: 0.85,
labelScale: 0.9,
facetPaddingScale: 2.1,
showLevels: true,
showLevelsLabels: false,
showAxesLabels: true,
showAxes: true,
showLegend: true,
showVertices: true,
showPolygons: true
};
}
// function getData
function getData($fileContent) {
ctrl.csv = $fileContent;
}
// function selectExample
function selectExample(item) {
var file = item + ".csv";
$http.get(file).success(function(data) {
ctrl.csv = data;
});
}
}
// directive function sunburst
function radar() {
return {
restrict: "E",
scope: {
csv: "=",
config: "="
},
link: radarDraw
};
}
// directive function onReadFile
function onReadFile($parse) {
return {
restrict: "A",
scope: false,
link: function(scope, element, attrs) {
var fn = $parse(attrs.onReadFile);
element.on("change", function(onChangeEvent) {
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function() {
fn(scope, {
$fileContent: onLoadEvent.target.result
});
});
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
});
}
};
}
})();
group axis value description
Mercedes mileage 7
Mercedes price 10
Mercedes safety 8
Mercedes performance 9
Mercedes interior 7
Mercedes warranty 7
Honda mileage 8
Honda price 6
Honda safety 9
Honda performance 6
Honda interior 3
Honda warranty 9
Chevrolet mileage 5
Chevrolet price 4
Chevrolet safety 6
Chevrolet performance 4
Chevrolet interior 5
Chevrolet warranty 6
group axis value description
Bulbs Jan 0
Bulbs Feb 0
Bulbs Mar 0
Bulbs Apr 0
Bulbs May 0
Bulbs Jun 0
Bulbs Jul 0
Bulbs Aug 1500
Bulbs Sep 5000
Bulbs Oct 8500
Bulbs Nov 3500
Bulbs Dec 500
Seeds Jan 2500
Seeds Feb 5500
Seeds Mar 9000
Seeds Apr 6500
Seeds May 3500
Seeds Jun 0
Seeds Jul 0
Seeds Aug 0
Seeds Sep 0
Seeds Oct 0
Seeds Nov 0
Seeds Dec 0
Flowers Jan 500
Flowers Feb 750
Flowers Mar 1500
Flowers Apr 2000
Flowers May 5500
Flowers Jun 7500
Flowers Jul 8500
Flowers Aug 7000
Flowers Sep 3500
Flowers Oct 2500
Flowers Nov 500
Flowers Dec 100
Trees Jan 0
Trees Feb 1500
Trees Mar 2500
Trees Apr 4000
Trees May 3500
Trees Jun 1500
Trees Jul 800
Trees Aug 550
Trees Sep 2500
Trees Oct 6000
Trees Nov 5500
Trees Dec 3000
group axis value description
Captain America Intelligence 3 only human
Captain America Strength 3 only human
Captain America Speed 2 only human
Captain America Durability 3 only human
Captain America Energy 1 only human
Captain America Fighting Skills 6 able to judge combat decisively
Iron Man Intelligence 6 Smart entreprenuer
Iron Man Strength 6 Powered by his suit
Iron Man Speed 5 rocket boosters
Iron Man Durability 6 tough durable material
Iron Man Energy 6
Iron Man Fighting Skills 4
Hulk Intelligence 6 Scientist brilliance
Hulk Strength 7 Insanely strong
Hulk Speed 3 clumsy
Hulk Durability 7 Close to industructible
Hulk Energy 1
Hulk Fighting Skills 4 great at SMASHING
Thor Intelligence 2 not too bright
Thor Strength 7 god-like strength
Thor Speed 7 god-like speed
Thor Durability 6 god-like durability
Thor Energy 6
Thor Fighting Skills 4 quite low for a god???
<!DOCTYPE html>
<html ng-app="RadarChart">
<head>
<meta charset="utf-8">
<title>D3 Radar Chart</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,600" />
<link rel="stylesheet" href="style.css" />
</head>
<body class="container" ng-controller="MainCtrl as radar">
<!-- header -->
<header class="page-header">
<h1>D3 Radar Chart</h1>
<p class="text-small">AngularJS application showcasing an interactive D3 radar chart (with facetting).</p>
</header>
<!-- main content -->
<div class="main container">
<h2>Visualization</h2>
<!-- visualization -->
<div class="visualization col-xs-7">
<p>Select example:
<select ng-options="example for example in radar.examples" ng-model="radar.exampleSelected" ng-change="radar.selectExample(radar.exampleSelected)"></select>
</p>
<div class="visualization">
<radar csv="radar.csv" config="radar.config"></radar>
</div>
</div>
<!-- configuration -->
<div class="configuration col-xs-5">
<form>
<h3>Configuration Parameters</h3>
<div class="form-group">
<label>Width:</label>
<input type="number" class="form-control-inline" step="50" ng-model="radar.config.w" />
<label>Height:</label>
<input type="number" class="form-control-inline" step="50" ng-model="radar.config.h" />
</div>
<div class="form-group">
<label>Levels:</label>
<input type="number" class="form-control-inline" step="1" ng-model="radar.config.levels" />
</div>
<div class="form-group">
<label>Padding Scale:</label>
<input type="number" class="form-control-inline" step="0.1" ng-model="radar.config.facetPaddingScale" />
</div>
<div class="form-group">
<label>Label Scale:</label>
<input type="number" class="form-control-inline" step="0.1" ng-model="radar.config.labelScale" />
</div>
<div class="form-group">
<label class="checkbox"><input type="checkbox" ng-model="radar.config.facet" /><span class="text-primary">Facet Plot</span></label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showLevels" />Levels</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showAxes" />Axes</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showVertices" />Vertices</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showPolygons" />Polygons</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showLegend" />Legend</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showLevelsLabels" />Levels Labels</label>
<label class="checkbox"><input type="checkbox" ng-model="radar.config.showAxesLabels" />Axes Labels</label>
</div>
<p class="text-muted">(NOTE: Not all configuration options are shown, refer to <code>README.md</code> or <code>radarDraw.js</code> for more details)</p>
</form>
</div>
</div>
<hr />
<!-- description -->
<div class="description">
<p>A radar chart visualizes multivariate data in a 2D chart of three or more quantitative variables represented on axes. Use the configuration parameters above to adjust the plot to your tastes, and you can also choose to view the plots stacked vs facetted.</p>
<p>For custom testing, load up a file conforming to the data schema (see Data section below) or you can test out the following sample files:</p>
<ul>
<li><a href="data_the_avengers.csv" target="_blank">The Avengers</a> (Power Grid ratings)</li>
<li><a href="data_plant_seasons.csv" target="_blank">Plant Seaons</a> (mock data)</li>
<li><a href="data_car_ratings.csv" target="_blank">Car Ratings</a> (mock data)</li>
</ul>
<input id="fileUpload" type="file" on-read-file="radar.getData($fileContent)" />
</div>
<hr/>
<!-- details -->
<div class="Details">
<h2>Details</h2>
<p>This is a variation of the <a href="http://bl.ocks.org/tpreusse/2bc99d74a461b8c0acb1">original</a> and <a href="http://bl.ocks.org/nbremer/6506614">improved</a> D3 radar chart. Main D3 drawing logic is located in the <code>radar.js</code> file.</p>
<p>Major improvements include:</p>
<ul>
<li>Refactoring D3 components (levels, labels, axes, polygons, legend), which now can be controlled through the <code>config</code> object (see configuration parameters).</li>
<li>Abstracting the building and rendering portions of the D3 visualization.</li>
<li>Aside from the basic stacked view, this variation includes a facetting option to plot the graphs in a facet grid.</li>
</ul>
<p>The data input takes the form of a csv file with the following schema:</p>
<ul>
<li><code>group (int/string):</code> data to be grouped into an object to plot the required polygon on the radar chart.</li>
<li><code>axis (int/string):</code> the axis of the radar charts (dimensions of the multivariate data).</li>
<li><code>value (int):</code> the data value of the given record.</li>
<li><code>description (int/string):</code> not a mandatory field, and additional columns after this are accepted as well.</li>
</ul>
<hr />
</div>
<!-- data/file preview -->
<div class="preview">
<h2>Data</h2>
<pre>{{ radar.csv }}</pre>
</div>
<!-- footer -->
<footer>
<p><a href="https://gist.github.com/chrisrzhou/2421ac6541b68c1680f8" target="_blank">D3 Radar Chart</a> by chrisrzhou, 2015-01-15
<br />
<a href="http://github.com/chrisrzhou" target="_blank"><i class="fa fa-github"></i></a> |
<a href="http://bl.ocks.org/chrisrzhou" target="_blank"><i class="fa fa-cubes"></i></a> |
<a href="http://www.linkedin.com/in/chrisrzhou" target="_blank"><i class="fa fa-linkedin"></i></a>
</p>
</footer>
<!-- scripts -->
<script src="http://code.angularjs.org/1.3.5/angular.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="app.js"></script>
<script src="radar.js"></script>
<script src="radarDraw.js"></script>
<script>
// Hack to make this example display correctly in an iframe on bl.ocks.org
d3.select(self.frameElement).style("height", "1000px");
</script>
</body>
</html>
/** RadarChart
*
* This is the main reuseable function to draw radar charts.
*
* The original d3 project is found on: https://github.com/alangrafu/radar-chart-d3
* This version is based on the cleaned version found on: http://bl.ocks.org/nbremer/6506614
* with some reorganization of code and added commenting, as well as further function abstractions
* to allow for addition/removal of visualization components via tweaking configuration parameters.
*
**/
var RadarChart = {
draw: function(id, data, options) {
// add touch to mouseover and mouseout
var over = "ontouchstart" in window ? "touchstart" : "mouseover";
var out = "ontouchstart" in window ? "touchend" : "mouseout";
/** Initiate default configuration parameters and vis object
*
**/
// initiate default config
var w = 300;
var h = 300;
var config = {
w: w,
h: h,
facet: false,
levels: 5,
levelScale: 0.85,
labelScale: 1.0,
facetPaddingScale: 2.5,
maxValue: 0,
radians: 2 * Math.PI,
polygonAreaOpacity: 0.3,
polygonStrokeOpacity: 1,
polygonPointSize: 4,
legendBoxSize: 10,
translateX: w / 4,
translateY: h / 4,
paddingX: w,
paddingY: h,
colors: d3.scale.category10(),
showLevels: true,
showLevelsLabels: true,
showAxesLabels: true,
showAxes: true,
showLegend: true,
showVertices: true,
showPolygons: true
};
// initiate main vis component
var vis = {
svg: null,
tooltip: null,
levels: null,
axis: null,
vertices: null,
legend: null,
allAxis: null,
total: null,
radius: null
};
// feed user configuration options
if ("undefined" !== typeof options) {
for (var i in options) {
if ("undefined" !== typeof options[i]) {
config[i] = options[i];
}
}
}
render(data); // render the visualization
/** helper functions
*
* @function: render: render the visualization
* @function: updateConfig: update configuration parameters
* @function: buildVis: build visualization using the other build helper functions
* @function: buildVisComponents: build main vis components
* @function: buildLevels: build "spiderweb" levels
* @function: buildLevelsLabels: build out the levels labels
* @function: buildAxes: builds out the axes
* @function: buildAxesLabels: builds out the axes labels
* @function: buildCoordinates: builds [x, y] coordinates of polygon vertices.
* @function: buildPolygons: builds out the polygon areas of the dataset
* @function: buildVertices: builds out the polygon vertices of the dataset
* @function: buildLegend: builds out the legend
**/
// render the visualization
function render(data) {
// remove existing svg if exists
d3.select(id).selectAll("svg").remove();
updateConfig();
if (config.facet) {
data.forEach(function(d, i) {
buildVis([d]); // build svg for each data group
// override colors
vis.svg.selectAll(".polygon-areas")
.attr("stroke", config.colors(i))
.attr("fill", config.colors(i));
vis.svg.selectAll(".polygon-vertices")
.attr("fill", config.colors(i));
vis.svg.selectAll(".legend-tiles")
.attr("fill", config.colors(i));
});
} else {
buildVis(data); // build svg
}
}
// update configuration parameters
function updateConfig() {
// adjust config parameters
config.maxValue = Math.max(config.maxValue, d3.max(data, function(d) {
return d3.max(d.axes, function(o) { return o.value; });
}));
config.w *= config.levelScale;
config.h *= config.levelScale;
config.paddingX = config.w * config.levelScale;
config.paddingY = config.h * config.levelScale;
// if facet required:
if (config.facet) {
config.w /= data.length;
config.h /= data.length;
config.paddingX /= (data.length / config.facetPaddingScale);
config.paddingY /= (data.length / config.facetPaddingScale);
config.polygonPointSize *= Math.pow(0.9, data.length);
}
}
//build visualization using the other build helper functions
function buildVis(data) {
buildVisComponents();
buildCoordinates(data);
if (config.showLevels) buildLevels();
if (config.showLevelsLabels) buildLevelsLabels();
if (config.showAxes) buildAxes();
if (config.showAxesLabels) buildAxesLabels();
if (config.showLegend) buildLegend(data);
if (config.showVertices) buildVertices(data);
if (config.showPolygons) buildPolygons(data);
}
// build main vis components
function buildVisComponents() {
// update vis parameters
vis.allAxis = data[0].axes.map(function(i, j) { return i.axis; });
vis.totalAxes = vis.allAxis.length;
vis.radius = Math.min(config.w / 2, config.h / 2);
// create main vis svg
vis.svg = d3.select(id)
.append("svg").classed("svg-vis", true)
.attr("width", config.w + config.paddingX)
.attr("height", config.h + config.paddingY)
.append("svg:g")
.attr("transform", "translate(" + config.translateX + "," + config.translateY + ")");;
// create verticesTooltip
vis.verticesTooltip = d3.select("body")
.append("div").classed("verticesTooltip", true)
.attr("opacity", 0)
.style({
"position": "absolute",
"color": "black",
"font-size": "10px",
"width": "100px",
"height": "auto",
"padding": "5px",
"border": "2px solid gray",
"border-radius": "5px",
"pointer-events": "none",
"opacity": "0",
"background": "#f4f4f4"
});
// create levels
vis.levels = vis.svg.selectAll(".levels")
.append("svg:g").classed("levels", true);
// create axes
vis.axes = vis.svg.selectAll(".axes")
.append("svg:g").classed("axes", true);
// create vertices
vis.vertices = vis.svg.selectAll(".vertices");
//Initiate Legend
vis.legend = vis.svg.append("svg:g").classed("legend", true)
.attr("height", config.h / 2)
.attr("width", config.w / 2)
.attr("transform", "translate(" + 0 + ", " + 1.1 * config.h + ")");
}
// builds out the levels of the spiderweb
function buildLevels() {
for (var level = 0; level < config.levels; level++) {
var levelFactor = vis.radius * ((level + 1) / config.levels);
// build level-lines
vis.levels
.data(vis.allAxis).enter()
.append("svg:line").classed("level-lines", true)
.attr("x1", function(d, i) { return levelFactor * (1 - Math.sin(i * config.radians / vis.totalAxes)); })
.attr("y1", function(d, i) { return levelFactor * (1 - Math.cos(i * config.radians / vis.totalAxes)); })
.attr("x2", function(d, i) { return levelFactor * (1 - Math.sin((i + 1) * config.radians / vis.totalAxes)); })
.attr("y2", function(d, i) { return levelFactor * (1 - Math.cos((i + 1) * config.radians / vis.totalAxes)); })
.attr("transform", "translate(" + (config.w / 2 - levelFactor) + ", " + (config.h / 2 - levelFactor) + ")")
.attr("stroke", "gray")
.attr("stroke-width", "0.5px");
}
}
// builds out the levels labels
function buildLevelsLabels() {
for (var level = 0; level < config.levels; level++) {
var levelFactor = vis.radius * ((level + 1) / config.levels);
// build level-labels
vis.levels
.data([1]).enter()
.append("svg:text").classed("level-labels", true)
.text((config.maxValue * (level + 1) / config.levels).toFixed(2))
.attr("x", function(d) { return levelFactor * (1 - Math.sin(0)); })
.attr("y", function(d) { return levelFactor * (1 - Math.cos(0)); })
.attr("transform", "translate(" + (config.w / 2 - levelFactor + 5) + ", " + (config.h / 2 - levelFactor) + ")")
.attr("fill", "gray")
.attr("font-family", "sans-serif")
.attr("font-size", 10 * config.labelScale + "px");
}
}
// builds out the axes
function buildAxes() {
vis.axes
.data(vis.allAxis).enter()
.append("svg:line").classed("axis-lines", true)
.attr("x1", config.w / 2)
.attr("y1", config.h / 2)
.attr("x2", function(d, i) { return config.w / 2 * (1 - Math.sin(i * config.radians / vis.totalAxes)); })
.attr("y2", function(d, i) { return config.h / 2 * (1 - Math.cos(i * config.radians / vis.totalAxes)); })
.attr("stroke", "grey")
.attr("stroke-width", "1px");
}
// builds out the axes labels
function buildAxesLabels() {
vis.axes
.data(vis.allAxis).enter()
.append("svg:text").classed("axis-labels", true)
.text(function(d) { return d; })
.attr("text-anchor", "middle")
.attr("x", function(d, i) { return config.w / 2 * (1 - 1.3 * Math.sin(i * config.radians / vis.totalAxes)); })
.attr("y", function(d, i) { return config.h / 2 * (1 - 1.1 * Math.cos(i * config.radians / vis.totalAxes)); })
.attr("font-family", "sans-serif")
.attr("font-size", 11 * config.labelScale + "px");
}
// builds [x, y] coordinates of polygon vertices.
function buildCoordinates(data) {
data.forEach(function(group) {
group.axes.forEach(function(d, i) {
d.coordinates = { // [x, y] coordinates
x: config.w / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * Math.sin(i * config.radians / vis.totalAxes)),
y: config.h / 2 * (1 - (parseFloat(Math.max(d.value, 0)) / config.maxValue) * Math.cos(i * config.radians / vis.totalAxes))
};
});
});
}
// builds out the polygon vertices of the dataset
function buildVertices(data) {
data.forEach(function(group, g) {
vis.vertices
.data(group.axes).enter()
.append("svg:circle").classed("polygon-vertices", true)
.attr("r", config.polygonPointSize)
.attr("cx", function(d, i) { return d.coordinates.x; })
.attr("cy", function(d, i) { return d.coordinates.y; })
.attr("fill", config.colors(g))
.on(over, verticesTooltipShow)
.on(out, verticesTooltipHide);
});
}
// builds out the polygon areas of the dataset
function buildPolygons(data) {
vis.vertices
.data(data).enter()
.append("svg:polygon").classed("polygon-areas", true)
.attr("points", function(group) { // build verticesString for each group
var verticesString = "";
group.axes.forEach(function(d) { verticesString += d.coordinates.x + "," + d.coordinates.y + " "; });
return verticesString;
})
.attr("stroke-width", "2px")
.attr("stroke", function(d, i) { return config.colors(i); })
.attr("fill", function(d, i) { return config.colors(i); })
.attr("fill-opacity", config.polygonAreaOpacity)
.attr("stroke-opacity", config.polygonStrokeOpacity)
.on(over, function(d) {
vis.svg.selectAll(".polygon-areas") // fade all other polygons out
.transition(250)
.attr("fill-opacity", 0.1)
.attr("stroke-opacity", 0.1);
d3.select(this) // focus on active polygon
.transition(250)
.attr("fill-opacity", 0.7)
.attr("stroke-opacity", config.polygonStrokeOpacity);
})
.on(out, function() {
d3.selectAll(".polygon-areas")
.transition(250)
.attr("fill-opacity", config.polygonAreaOpacity)
.attr("stroke-opacity", 1);
});
}
// builds out the legend
function buildLegend(data) {
//Create legend squares
vis.legend.selectAll(".legend-tiles")
.data(data).enter()
.append("svg:rect").classed("legend-tiles", true)
.attr("x", config.w - config.paddingX / 2)
.attr("y", function(d, i) { return i * 2 * config.legendBoxSize; })
.attr("width", config.legendBoxSize)
.attr("height", config.legendBoxSize)
.attr("fill", function(d, g) { return config.colors(g); });
//Create text next to squares
vis.legend.selectAll(".legend-labels")
.data(data).enter()
.append("svg:text").classed("legend-labels", true)
.attr("x", config.w - config.paddingX / 2 + (1.5 * config.legendBoxSize))
.attr("y", function(d, i) { return i * 2 * config.legendBoxSize; })
.attr("dy", 0.07 * config.legendBoxSize + "em")
.attr("font-size", 11 * config.labelScale + "px")
.attr("fill", "gray")
.text(function(d) {
return d.group;
});
}
// show tooltip of vertices
function verticesTooltipShow(d) {
vis.verticesTooltip.style("opacity", 0.9)
.html("<strong>Value</strong>: " + d.value + "<br />" +
"<strong>Description</strong>: " + d.description + "<br />")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px");
}
// hide tooltip of vertices
function verticesTooltipHide() {
vis.verticesTooltip.style("opacity", 0);
}
}
};
function radarDraw(scope, element) {
/**
* Angular variables
*
*/
// watch for changes on scope.data
scope.$watch("[csv, config]", function() {
var csv = scope.csv;
var config = scope.config;
var data = csv2json(csv);
RadarChart.draw(element[0], data, config); // call the D3 RadarChart.draw function to draw the vis on changes to data or config
});
// helper function csv2json to return json data from csv
function csv2json(csv) {
csv = csv.replace(/, /g, ","); // trim leading whitespace in csv file
var json = d3.csv.parse(csv); // parse csv string into json
// reshape json data
var data = [];
var groups = []; // track unique groups
json.forEach(function(record) {
var group = record.group;
if (groups.indexOf(group) < 0) {
groups.push(group); // push to unique groups tracking
data.push({ // push group node in data
group: group,
axes: []
});
};
data.forEach(function(d) {
if (d.group === record.group) { // push record data into right group in data
d.axes.push({
axis: record.axis,
value: parseInt(record.value),
description: record.description
});
}
});
});
return data;
}
}
body {
font-family: "Open Sans", sans-serif;
font-size: 12px;
font-weight: 400;
padding-top: 10px;
padding-bottom: 100px;
}
html {
overflow-y: scroll;
}
h1 {
color: steelblue;
font-weight: 800;
font-size: 1.7em;
}
h2 {
color: steelblue;
font-size: 1.3em;
padding-bottom: 10px;
}
h3 {
color: gray;
font-size: 1.2em;
padding-bottom: 10px;
}
footer a,
footer a:hover, footer a:visited {
color: #D2A000;
}
.text-small {
font-size: 12px;
font-style: italic;
}
footer {
color: white;
padding-top: 5px;
border-top: 1px solid gray;
font-size: 12px;
position: fixed;
left: 0;
bottom: 0;
height: 50px;
width: 100%;
background: black;
text-align: center;
}
pre {
height: 200px;
font-size: 9px;
overflow-y: scroll;
}
.form-control-inline {
display: inline;
width: 40px;
margin-right: 5px;
}
.visualization {
}
.configuration {
font-size: 0.8em;
width: 250px;
border: 1px solid #ddd;
padding-left: 20px;
margin-bottom: 20px;
}
.checkbox {
margin-left: 20px;
}
/* Customizable classes used in D3 vis, uncomment to customize
.svg-vis {
background-color: gray;
opacity: 0.5;
}
.verticesTooltip {
position: absolute;
color: red;
font-size: 12px;
width: 100px;
height: auto;
padding: 5px;
border: 2px solid gray;
border-radius: 5px;
pointer-events: none;
opacity: 0;
background: #f4f4f;
}
.level-lines {
stroke: red;
stroke-width: 1px;
}
.level-labels {
fill: red;
font-size: 12px;
}
.axis-lines {
stroke: blue;
stroke-width: 2px;
}
.axis-labels {
fill: blue;
font-size: 12px;
}
.polygon-vertices {
fill-opacity: 0.6;
}
.polygon-areas {
fill-opacity: 0.6;
}
.legend-tiles {
fill-opacity: 0.3;
}
.legend-labels {
font-size: 15px;
}
*/
@PrimordialLife
Copy link

Asesome! I really love this 3d radar chart. However, I would be willing to PAY someone to make it look as good in other browsers as it does in chrome. The feature doesn't look the same in browsers besides chrome. I can show you screenshots upon request.

@nhanbt
Copy link

nhanbt commented Dec 13, 2017

Loving this! Any chance you could include a permissive open source license here (MIT maybe?) so we can use it in our project, @chrisrzhou?

@ericsangabriel
Copy link

This really awesome!!!!

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