D3 Usage - RooyyDoe/functional-programming GitHub Wiki

During the functional programming course we have to make a data visualization of the NMVW collection. To do this we must use the D3.js library. I will be using and remaking a existing data visualization that is made and owned by Nadieh Bremer

Also look at my D3.js code breakdown

Nadieh Bremer (radarChart)

I am using the library that Nadieh Bremer made for a radarChar. I want to strip down this library and figure out how every part works and try to adjust it to make it my own. Every part of the code will be explained in this documentation and I also will show the changes that I have made in the code of Nadieh Bremer.

radarChart data format

In my cleaningFunctions I am cleaning the data so that it can be used in d3. At first I am making a object to get all the continent and category Uri's after this I am getting the total items of every category in the continents and then I'll count them and get the percentage of it. When the data went through all the cleaning functions the data would look like this:

{ Axis: categories.name, percentage: categories.percentage}

The radarChart needs a certain format data, if it receives a different format, the library code no longer works and no visualization can be made. The library expects a code format that looks like this:


  var data = [
    [//iPhone
    {axis:"Battery Life",value:0.22},
    {axis:"Brand",value:0.28},
    {axis:"Contract Cost",value:0.29},
    {axis:"Design And Quality",value:0.17},
    {axis:"Have Internet Connectivity",value:0.22},
    {axis:"Large Screen",value:0.02},
    {axis:"Price Of Device",value:0.21},
    {axis:"To Be A Smartphone",value:0.50}			
    ],[//Samsung
    {axis:"Battery Life",value:0.27},
    {axis:"Brand",value:0.16},
    {axis:"Contract Cost",value:0.35},
    {axis:"Design And Quality",value:0.13},
    {axis:"Have Internet Connectivity",value:0.20},
    {axis:"Large Screen",value:0.13},
    {axis:"Price Of Device",value:0.35},
    {axis:"To Be A Smartphone",value:0.38}
    ],[//Nokia Smartphone
    {axis:"Battery Life",value:0.26},
    {axis:"Brand",value:0.10},
    {axis:"Contract Cost",value:0.30},
    {axis:"Design And Quality",value:0.14},
    {axis:"Have Internet Connectivity",value:0.22},
    {axis:"Large Screen",value:0.04},
    {axis:"Price Of Device",value:0.41},
    {axis:"To Be A Smartphone",value:0.30}
    ]
  ];

As the code above here you need to have one main array with objects into it. This can also be only one object but in my case I will have five different objects in this array. As you see it needs two values inside the object the name and value this is the percentage I have calculated with my code.

After all the cleaning functions have cleaned en transformed the data it will most likely look like this:

Insert data into D3.js

You can be able to change up the options when you send through the data that is needed for the radarChart. These values you need to give through are options like: Color, Width, Height, Margin. On this way you can specify your own options without changing the library. It would look something like this:


const margin = {top: 100, right: 100, bottom: 100, left: 100},
	width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
	height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
		
const color = d3.scale.ordinal()
	.range(['#EDC951','#CC333F','#00A0B0', '#002533', '#4D5B23']);
		
const radarChartOptions = {
	w: width,
	h: height,
	margin: margin,
	maxValue: 0.5,
	levels: 7,
	color: color
};

You can change pretty much everything in these options. So if you are just mainly using this library and doing nothing else with it you can edit all your different options here.

For my project I wanted to get into the library and change things from there. On this way I could make it a bit more personal and I was hoping to understand the library and what Nadieh Bremer did with the code.

Changes in the code of Nadieh Bremer

Because I wanted to understand the library of Nadieh Bremer I needed to strip it down and build it up again on my own way. Of course a lot of her code is stil being used but I have edited and removed some code that I did not really need or couldn't figure out what it did.

Styling options in the radarChart.js (Props)


let cfg = {
	w: 1200, //Width of the circle
	h: 520, //Height of the circle
	margin: { top: 40, right: 40, bottom: 40, left: 40 }, //The margins of the SVG
	levels: 4, //How many levels or inner circles should there be drawn
	maxValue: 0, //value of the biggest circle in the radarChart
	labelFactor: 1.1, //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.3, //The opacity of the area of the blob
	dotRadius: 3, //The size of the colored circles of each blob
	opacityCircles: .7, //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: color //Color function
};

Changed almost every anonymous function to a arrow function for consistency


let maxValue = Math.max(cfg.maxValue, d3.max(data, (i) => {
	return d3.max(i.map((o) => {
		return o.value;
	}));
}));

Instead of hardcoded data I have API data added into the radarChart


const data = cleanData;		
radarChart('.radarChart', data, color);

I have changed the styling options to only giving through the color


const color = d3.scale.ordinal()
	.range(['#EDC951','#CC333F','#00A0B0', '#002533', '#4D5B23']);

I have removed the option checker in the library because I had no options


if ('undefined' !== typeof options) {
        for (var i in options) {
            if ('undefined' !== typeof options[i]) {
                cfg[i] = options[i];
            }
        } 
    } 

I also have removed the tooltip function because the blob-dots are to close on each other


//Wrapper for the invisible circles on top
    var blobCircleWrapper = g.selectAll(".radarCircleWrapper")
        .data(data)
        .enter().append("g")
        .attr("class", "radarCircleWrapper");

    //Append a set of invisible circles on top for the mouseover pop-up
    blobCircleWrapper.selectAll(".radarInvisibleCircle")
        .data(function (d, i) {
            return d;
        })
        .enter().append("circle")
        .attr("class", "radarInvisibleCircle")
        .attr("r", cfg.dotRadius * 1.5)
        .attr("cx", function (d, i) {
            return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
        })
        .attr("cy", function (d, i) {
            return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
        })
        .style("fill", "none")
        .style("pointer-events", "all")
        .on("mouseover", function (d, i) {
            newX = parseFloat(d3.select(this).attr('cx')) - 10;
            newY = parseFloat(d3.select(this).attr('cy')) - 10;

            tooltip
                .attr('x', newX)
                .attr('y', newY)
                .text(Format(d.value))
                .transition().duration(200)
                .style('opacity', 1);
        })
        .on("mouseout", function () {
            tooltip.transition().duration(200)
                .style("opacity", 0);
        });

    //Set up the small tooltip for when you hover over a circle
    var tooltip = g.append("text")
        .attr("class", "tooltip")
        .style("opacity", 0);

Last thing I have removed out of the library was the text-wrap, because I did not know how it worked


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