Lightning · Salesforce

CSV PARSING AND GENERATING CHARTS IN LIGHTNING COMPONENTS

Lightning offers advantage over classic that it allows a lot of client processing thus saving the server side actions. One of the scenarios that I would like to cover is parsing CSV file and generating reports from the data fetched without any server side actions.

Let us consider, I have a CSV file (comma separated ) that contains information about students who play different sports in different age groups.

Let us first learn how to generate a Donut Chart in a lightning component to depict the count of the number of students playing different sports.

Download the d3.js file from https://d3js.org/d3.v4.js and upload as a static resource in your Salesforce Org. D3.js a JavaScript library used for visualization purpose. D3 can be used right from generating HTML tables to bar graphs.

Now let’s get started with the lightning component.

This blog will demonstrate how to:

  1. Parse a CSV file
  2. Generate a Donut Chart
  3. Generate a Grouped Bar chart

Generate a CSV file based on your requirement. Consider a file as shown below:

Capture-1

First we will parse the CSV file, to generate the required format necessary for generating a Donut Chart.

We will calculate the count of student playing different sports.

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global">

	<ltng:require scripts="/resource/lodash/lodash/lodash.js" />

	<ltng:require scripts="{!$Resource.d3}" />
<div class="scroll" style="width:100%">
<div style="padding-right:5%">
			<!-- csv file  input -->
											<lightning:input type="file" label="" name="Mapping" multiple="false" accept=".csv" onchange="{!c.readCSV}" /></div>
<div style="width:960px" class="slds-scrollable" id="chart"></div>
<div style="width:960px" class="slds-scrollable" id="piechart"></div>
</div>
</aura:component>

Controller.js

({
	readCSV: function(component, event, helper) {

        var filename = event.getSource().get("v.files");

        var textdata;
        var reader = new FileReader();
        var infolst = [];
        reader.onload = function() {

            var text = reader.result; /*Get the data stored in file*/
            console.log(reader.result.substring(0, 200));
            console.log('Data from CSV file' + text);
            textdata = text;
            var rows = textdata.split('\n'); /*Spilt based on new line to get each row*/
            console.log('File header' + rows[0]);
            component.set("v.showSpinner", false);

            /* Ignore the first row (header)  and start from second*/
            for (var i = 1; i <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>< rows.length; i = i + 1) {
                console.log('Length', +rows.length); //total number of rows in the file including header
                /*Spilt based on the comma*/
                var cells = rows[i].split(',');
                console.log('One row' + cells);
                console.log('Row length' + cells.length);

                if(cells.length!=1){
                    var sports=cells[3].split('\r');
                var cellinfo = {
                    'StudentNo': cells[0],
                    'Name': cells[1],
                    'Date': cells[2],
                    'Sports': sports[0],
                };

                infolst.push(cellinfo);
                component.set("v.data",infolst);
                }

            }

            helper.generatechartdata(component);

        };
        if (filename[0] !== undefined && filename[0] !== null && filename[0] !== '') {
            reader.readAsText(filename[0]);
        }

    },

Helper.js

({
	displaydata: function(component, data) {

		d3.select("#chart")
			.select("svg")
			.remove();

		var margin = {
				top: 20,
				right: 20,
				bottom: 30,
				left: 40
			},

			width = 800 - margin.left - margin.right,
			height = 200 - margin.top - margin.bottom;

		var x0 = d3.scaleBand()
			.rangeRound([0, width]);

		var x1 = d3.scaleBand();

		var y = d3.scaleLinear()
			.rangeRound([height, 0]);

		var xAxis = d3.axisBottom(x0)
			.tickSize(0);

		var yAxis = d3.axisLeft(y);

		var color = d3.scaleOrdinal()
			.range(["#CD853F", "#6A5ACD", "#2F4F4F", "#6B8E23"]);

		var svg = d3.select('#chart')
			.append("svg")
			.attr("width", width + margin.left + margin.right)
			.attr("height", height + margin.top + margin.bottom)
			.append("g")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

		var categoriesNames = data.map(function(d) {
			return d.type;
		});
		var rateNames = data[0].values.map(function(d) {
			return d.agerange;
		});
		console.log(categoriesNames);

		x0.domain(categoriesNames);
		x1.domain(rateNames)
			.range([0, x0.bandwidth()]);

		y.domain([0, d3.max(data, function(categorie) {
			return d3.max(categorie.values, function(d) {
				return d.count;
			});
		})]);

		svg.append("g")
			.attr("class", "x axis")
			.attr("transform", "translate(0," + height + ")")
			.call(xAxis);

		svg.append("g")
			.attr("class", "y axis")
			.style('opacity', '0')
			.call(yAxis)
			.append("text")
			.attr("transform", "rotate(-90)")
			.attr("y", 6)
			.attr("dy", ".71em")
			.style("text-anchor", "end")
			.style('font-weight', 'bold')
			.text("Value");

		svg.select('.y')
			.transition()
			.duration(500)
			.delay(1300)
			.style('opacity', '1');

		var slice = svg.selectAll(".slice")
			.data(data)
			.enter()
			.append("g")
			.attr("class", "g")
			.attr("transform", function(d, i) {
				console.log(i);
				console.log(x0(d.type));
				var s = x0(d.type) + 15 * i;
				return "translate(" + s + ",0)";
			});

		slice.selectAll("rect")
			.data(function(d) {
				return d.values;
			})
			.enter()
			.append("rect")
			.attr("width", x1.bandwidth())
			.attr("x", function(d) {
				return x1(d.agerange);
			})
			.style("fill", function(d) {
				return color(d.agerange)
			})
			.attr("y", function(d) {
				return y(0);
			})
			.attr("height", function(d) {
				return height - y(0);
			})
			.on("mouseover", function(d) {
				d3.select(this)
					.style("fill", d3.rgb(color(d.agerange))
						.darker(2));
			})
			.on("mouseout", function(d) {
				d3.select(this)
					.style("fill", color(d.agerange));
			});

		slice.selectAll("rect")
			.transition()
			.delay(function(d) {
				return Math.random() * 1000;
			})
			.duration(1000)
			.attr("y", function(d) {
				return y(d.count);
			})
			.attr("height", function(d) {
				return height - y(d.count);
			});

		//Legend
		var legend = svg.selectAll(".legend")
			.data(data[0].values.map(function(d) {
					return d.agerange;
				})
				.reverse())
			.enter()
			.append("g")
			.attr("class", "legend")
			.attr("transform", function(d, i) {
				var h = (i * 20) - 20;
				return "translate(20," + h + ")";
			})
			.style("opacity", "0");

		legend.append("rect")
			.attr("x", width - 10) //18
			.attr("width", 10) //18
			.attr("height", 10) //18
			.style("fill", function(d) {
				return color(d);
			});

		legend.append("text")
			.attr("x", width - 10) //18
			.attr("y", 5) // 9
			.attr("dy", ".35em")
			.style("text-anchor", "end")
			.text(function(d) {
				return d;
			});

		legend.transition()
			.duration(500)
			.delay(function(d, i) {
				return 1300 + 100 * i;
			})
			.style("opacity", "1");

		component.set("v.showSpinner", false);

	},

	generatechartdata: function(component, event, helper) {
		var infolst = component.get("v.data");
		var aMap = {};

		function addValueToKey(key, value) {
			aMap[key] = aMap[key] || [];
			var tempArr = aMap[key];
			tempArr.push(value);
			aMap[key] = tempArr;
			aMap[key] = _.sortBy(aMap[key], function(x) {
				return x.StudentNo;
			});
		}
		for (var i = 0; i < infolst.length; i = i + 1) {
			addValueToKey(infolst[i].StudentNo, infolst[i]);
		}
		console.log('Map' + JSON.stringify(aMap));
		var studentlist = [];
		var key = Object.keys(aMap);
		console.log('Keys' + key);
		var today_date = new Date();
		var today_year = today_date.getFullYear();
		//console.log(today_date);
		for (var i = 0; i < key.length; i = i + 1) {
			var itemlist = aMap[key[i]];
			var age;
			var agerange;

			var Sportslist = [];
			_.forEach(itemlist, function(o) {
				Sportslist.push(o.Sports);

			});

			var sports;
			for (var j = 0; j < itemlist.length; j = j + 1) { 				var type; 				console.log('Fields' + itemlist[j].Date); 				if (itemlist[j].Date !== undefined) { 					var dob = itemlist[j].Date.split('/'); 				} 				// var newdate=new Date(dob[2], dob[1] * 1 , dob[0]); 				age = today_year - dob[2]; 				console.log('Age' + age); 				if (age >= 0 && age <= 5) { 					agerange = '0-5'; 				} else if (age > 5 && age <= 10) { 					agerange = '6-10'; 				} else if (age > 10 && age <= 15) { 					agerange = '11-15'; 				} else if (age > 15 && age <= 18) {
					agerange = '15-18';
				}

				sports = itemlist[j].Sports;

			}

			var Student = {
				'StudentNo': key[i],
				'ageRange': agerange,
				'sports': sports,

			};

			studentlist.push(Student);

		}

		var rangelist = ['0-5', '6-10', '11-15', '15-18'];
		var typelist = ['Cricket', 'Football', 'Basketball', 'Hockey'];
		var count = [];
		var countdata = [];
		var data = [];

		var objdatalist = [];
		var pielist = [];

		for (var m = 0; m < typelist.length; m = m + 1) {

			var objlist = [];

			for (var n = 0; n <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>< rangelist.length; n = n + 1) {

				// count[m]=[];
				count[n] = _.filter(studentlist, function(o) {
						return (o.ageRange == rangelist[n] && o.sports == typelist[m]);

					})
					.length;

				data.push(count[n]);
				var Values = {
					'agerange': rangelist[n],
					'count': count[n],
				};

				objlist.push(Values);

			}

			var objdata = {
				'type': typelist[m],
				'values': objlist
			};

			objdatalist.push(objdata);

			countdata[m] = _.filter(studentlist, function(o) {
					return (o.sports == typelist[m]);

				})
				.length;
			var piedata = {
				'type': typelist[m],
				'count': countdata[m]
			};
			pielist.push(piedata);

		}
		console.log('Data' + JSON.stringify(objdatalist));

		this.displaydata(component, objdatalist);
		this.displaypiechart(component, pielist);
	},

	displaypiechart: function(component, dataset) {

		var pie = d3.pie()
			.value(function(d) {
				return d.count;
			})
			(dataset);

		var w = 300,
			h = 300;

		var outerRadius = w / 2;
		var innerRadius = 100;

		var color = d3.scaleOrdinal()
			.range(["#CD853F", "#6A5ACD", "#2F4F4F", "#6B8E23"]);

		var arc = d3.arc()
			.outerRadius(outerRadius)
			.innerRadius(innerRadius);

		var svg = d3.select('#piechart')
			.append("svg")
			.attr("width", w)
			.attr("height", h)
			.append("g")

			.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");

		var path = svg.selectAll('path')
			.data(pie)
			.enter()
			.append('path')
			.attr("d", arc)
			.attr(
				"fill",
				function(d, i) {
					return color(d.data.type);
				}
			);

		path.transition()
			.duration(1000)
			.attrTween('d', function(d) {
				var interpolate = d3.interpolate({
					startAngle: 0,
					endAngle: 0
				}, d);
				return function(t) {
					return arc(interpolate(t));
				};
			});

		var restOfTheData = function() {

			var text = svg.selectAll('text')
				.data(pie)
				.enter()
				.append("text")
				.transition()
				.duration(200)
				.attr("transform", function(d, i) {

					return "translate(" + arc.centroid(d) + ")";
				})
				.attr("dy", ".4em")
				.attr("text-anchor", "middle")
				.text(function(d) {

					return d.data.count;
				})
				.style(
					"fill", '#fff',
					"font-size", '10px'
				);

			var legendRectSize = 20;
			var legendSpacing = 7;
			var legendHeight = legendRectSize + legendSpacing;

			var legend = svg.selectAll('.legend')
				.data(color.domain())
				.enter()
				.append('g')
				.attr(

					"transform",
					function(d, i) {
						//Just a calculation for x & y position

						return 'translate(-35,' + ((i * legendHeight) - 65) + ')';
					}
				)
				.attr("class", 'legend');

			legend.append('rect')
				.attr(
					"width", legendRectSize
				)
				.
			attr("height", legendRectSize

				)
				.attr("rx", "20")
				.attr("ry", "20")
				.style(
					"fill",
					function(d) {
						return color(d);
					}

				);

			legend.append('text')
				.attr(
					"x", "30"

				)
				.attr("y", "15")
				.text(function(d) {
					return d;
				})
				.style(
					"fill", '#929DAF',
					"font-size", '14px'
				);
		};

		setTimeout(restOfTheData, 1000);
	}
})

Upload the same file format as shown above:

The following Donut chart will be generated. The chart below shows the count of  students playing a particular sports.

Capture-2

Similarly we can also generate Bar Graph similar to the chart above. The graph below depicts the number of students who play a particular sports grouped based on their age group.

capture4

Now if we look into the code closely, you will observe that the function generatechartdata() will create a JSON object required to be passed to the d3 code to get the graph in the desired format.

For donut chart the data generated will be similar  in the form:

Capture5

For Bar Graph the JSON Object generated would be in the format below:

Capture6

Some of the D3 function used are:

d3.arc – Used to generate an arc for the donut chart.

d3.pie –  Used to generate a pie with the JSON data returning another JSON object as shown below.

Capture7
transform.translate(x, y) – Returns a transform whose translation tx1 and ty1 is equal to tx0 + x and ty0 + y, where tx0 and ty0 is this transform’s translation.

d3.legend – Is used to add a legend to the d3 chart.

For generating d3 charts of different types : https://bl.ocks.org/

For complete information about  d3 functions: https://github.com/d3/d3/blob/master/API.md

Find the complete code on:  https://github.com/AryaRamani/d3chartgeneration

So next time you have a functionality to generate reports in your custom lightning components, just refer to the d3.js library.

One thought on “CSV PARSING AND GENERATING CHARTS IN LIGHTNING COMPONENTS

  1. Nice blog right here! Additionally your site so much up fast! What host are you the use of? Can I am getting your associate link to your host? I wish my site loaded up as fast as yours lol

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s