Scatter Plot Chart - Tuong-Nguyen/Angular-D3-Cometd GitHub Wiki

Intro

The chart show the result of customer survey conducted by fictional donut company that display the rating about each kind of donuts with customer ages.

  • y axis shows the rate.
  • x axis shows the age.
  • Each donut has its own color.
  • The size of the circle shows the number of people in that age who do the survey.
  • When user hovers to a circle, all circles of the donuts are highlight and the information of the selected circle is displayed on the top of the chart.

Steps for creating the chart

  1. Initialize the canvas/svg for drawing the chart.
  2. Configure the scales for specified the position of data in the canvas.
  3. Draw axis (x and y) and their labels
  4. Draw the grid lines (using ticks of the axis)
  5. Add circles for data into the Canvas
  6. Move the circles to correct positions and radius of circles
  7. Set colors for circles base on its types
  8. Add events to circle for highlighting circles of the same donut and display information of the circle on top of the chart when hover on the circle.

Below is the detail about how to create Scatter Plot Chart.

Init Canvas/SVG

Config the size of canvas/svg that you will be used for drawing.

var w = 800;
var h = 450;
var margin = {
	top: 60,
	bottom: 80,
	left: 100,
	right: 80
};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
			.attr("id", "chart")
			.attr("width", w)
			.attr("height", h);
var chart = svg.append("g")
			.classed("display", true)
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Config Scales

You must config scales for axis x,y to let d3 known how resizing data will be fit pre-defined SVG Coordinate space.

// Scale for coloring circles of each donut type
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);

// Scale for x axis
var x = d3.scaleLinear()
		.domain(d3.extent(data,function(d){
			return d.age;
		}))
		.range([0, width]);
// Scale for y axis: from Height -> 0
var y = d3.scaleLinear()
		.domain([1,5])
		.range([height,0]);

Draw Axis and Grid Lines

Draw Axis

// Declare axis x/y variables
var xAxis = d3.axisBottom()
			.scale(x)
			.tickValues(tickValues);

var yAxis = d3.axisLeft()
			.scale(y)
			.ticks(5)
			.tickSize(20)
			.tickFormat(function(d){				
                            // Format the content of each tick
                            return d.toFixed(1);
			});

Draw grid lines

var xGridlines = d3.axisBottom()
					.scale(x)
					.tickValues(tickValues)
					.tickSize(height,height)
					.tickFormat("");
					
var yGridlines = d3.axisLeft()
					.scale(y)
					.tickSize(-width,0,0)
					.tickFormat("");

var responseScale = d3.scaleLinear()
		.domain(d3.extent(data, function(d){
			return d.responses;
		}))
		.range([2,15]);

Group them all in in function drawAxis

function drawAxis(params){
        
    // Draw GridLines
    this.append("g")
        .call(params.gridlines.x)
        .classed("gridline x", true)
        .attr("transform", "translate(" + 0 + "," + 0 + ")");
    this.append("g")
        .call(params.gridlines.y)
        .classed("gridline y", true)
        .attr("transform", "translate(" + 0 + "," + 0 + ")");
    this.append("g")
        .call(params.axis.x)
        .classed("axis x", true)
        .attr("transform", "translate(" + 0 + "," + height + ")");
    this.append("g")
        .call(params.axis.y)
        .classed("axis y", true)
        .attr("transform", "translate(" + 0 + "," + 0+ ")");

    // Draw Label for Axis
    this.select(".y.axis")
        .append("text")
        .classed("y axis-label", true)
        .attr("transform", "translate(" + -56 + "," + height/2+ ") rotate(-90)")
        .text("Rating (1=Low, 5=High)");
    this.select(".x.axis")
        .append("text")
        .classed("x axis-label", true)
        .attr("transform", "translate(" + width/2 + "," + 48 + ")")
        .text("Customer age");

    this.append("g")
        .append("text")
        .classed("chart-header", true)
        .text("")
        .attr("transform", "translate(0," + -24 + ")")
}

Bind data to chart

function plot(params){
	var self = this;
	//Get each of the donut types
	var donuts = d3.keys(params.data[0]).filter(function(d){
		return d !== "age" && d !== "responses";
	});
	drawAxis.call(this,params);
	//Create a group for each type of donut
	this.selectAll(".donut")
		.data(donuts)
		.enter()
			.append("g")
			.attr("class", function(d){
				return d;
			})
			.classed("donut", true);

	this.selectAll(".donut")
		.style("fill", function(d,i){ return colorScale(i); })
		.on("mouseover", function(d,i){
			d3.select(this)
				.classed("highlight", true)
				.transition()
				.style("opacity", 1);
		})
		.on("mouseout", function(d,i){
			d3.select(this)
				.classed("highlight", false)
				.transition()
				.style("opacity", 0.1);
		});

	donuts.forEach(function(donut){
		var g = self.selectAll("g."+donut);
		var arr = params.data.map(function(d){
				return {
					key: donut,
					value: d[donut],
					age: d.age,
					responses: d.responses
				};
			});
		//Setup circles
		g.selectAll(".response")
			.data(arr)
			.enter()
				.append("circle")
				.classed("response", true);
		//Update circles
		g.selectAll(".response")
			.attr("r", function(d){ return responseScale(d.responses); })
			.attr("cx", function(d){ return x(d.age); })
			.attr("cy", function(d){ return y(d.value); })
			.on("mouseover", function(d,i){
				var str = d.key + " Donut: ";
				str += "Age: " + d.age + ", ";
				str += "Responses: " + d.responses + ", ";
				str += "Average Rating: " + d.value;
				str += ""
				d3.select(".chart-header").text(str);
			})
			.on("mouseout", function(d,i){
				d3.select(".chart-header").text("");
			});
        
		//Remove any unbound elements
		g.selectAll(".response").data(arr).exit().remove();
	});
}

Code

https://github.com/Tuong-Nguyen/Angular-D3-Cometd/tree/master/Scatter%20Plot%20Chart