Skip to content

Instantly share code, notes, and snippets.

@induprasad
Last active May 3, 2018 12:56
Show Gist options
  • Save induprasad/6cc57979b6deb6fe001e9fd4f1644ee2 to your computer and use it in GitHub Desktop.
Save induprasad/6cc57979b6deb6fe001e9fd4f1644ee2 to your computer and use it in GitHub Desktop.
Lightning component for forming Chart
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,forceCommunity:availableForAllPageTypes" access="global" >
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" />
<ltng:require scripts="/resource/StrikeCmpResource" afterScriptsLoaded="{!c.onInit}"/>
<aura:attribute name="scriptsLoaded" type="Boolean" default="{!false}"/>
<aura:attribute name="chartRendered" type="Boolean" default="{!false}" description="Flag to display the loading spinner."/>
<aura:attribute name="triggerRedraw" type="Boolean" access="private" default="{!false}" description="Flag that triggers redraw of the chart."/>
<aura:attribute name="displayAxis" type="Boolean" access="private" default="{!false}" description="Flag that triggers displaying the left and bottom axis labels."/>
<aura:attribute name="containerWidth" type="Integer" access="private" default="0" description="This is the avaliable width to draw our charts in pixels"/>
<aura:attribute name="yAxisLabelMaxHeight" type="Integer" access="private" default="100" description="This is the max width the y axis can grow to. It should be derived from chart height."/>
<aura:attribute name="thresholdLabel" type="String" default="Threshhold" description="Label that appears at the threshold value."/>
<aura:attribute name="thresholdValue" type="Integer" description="Value where the threshold line appears. "/>
<!-- CHANGE THE DOM AND FORCE RUN RERENDERER, WITHOUT CHANGING THE DOM RERENDER DOESN'T RUN -->
<div class="slds-hide">{!v.triggerRedraw}</div>
<aura:attribute name="type" type="String" required="{!true}" description="Type of the chart user wants to render."/>
<aura:attribute name="title" type="String" description="Title of the chart. Shows up at the top-left corner of the chart."/>
<aura:attribute name="leftSubtitle" type="String" description="Subtitle of the chart. Shows up at the bottom-left corner of the chart."/>
<aura:attribute name="rightSubtitle" type="String" description="Subtitle of the chart. Shows up at the bottom-right corner of the chart."/>
<aura:attribute name="data" type="Object" required="{!true}" description="The data based on which chart renders."/>
<aura:attribute name="fontSize" type="String" access="private" default=".8125rem" description="The font size of the div texts"/>
<!-- TOOLTIP SPECIFIC ATTRIBUTES -->
<aura:attribute name="tooltipHtml" type="String" access="private" description="Inner html for the tooltip div. This gets generated on mouseover of a datapoint"/>
<aura:attribute name="tooltipDisplay" type="String" access="private" default="none" description="Determines to show the tooltip or not"/>
<aura:attribute name="tooltipXPos" type="Integer" access="private" default="0" description="X position of the tooltip"/>
<aura:attribute name="tooltipYPos" type="Integer" access="private" default="0" description="Y position of the tooltip"/>
<aura:attribute name="tooltipOpacity" type="Integer" access="private" default="0" description="Opacity level of the tooltip. This is used to hide it visibly while the width is calculated"/>
<!-- AREA CHART SPECIFIC ATTRIBUTES -->
<aura:attribute name="xAxisLabel" type="String" default="x-axis" description="Label that appears along X axis."/>
<aura:attribute name="yAxisLabel" type="String" default="y-axis" description="Label that appears along Y axis."/>
<aura:attribute name="xAxisDataType" type="String" default="Number" description="Data type on X axis."/>
<aura:attribute name="yAxisDataType" type="String" default="Number" description="Data type on Y axis."/>
<!-- BAR CHART SPECIFIC ATTRIBUTES -->
<aura:attribute name="orientation" type="String" default="vertical" description="Determines orientation of the bar chart. It defaults to vertical."/>
<!-- Not Used -->
<!-- BUBBLE CHART SPECIFIC ATTRIBUTES -->
<aura:attribute name="bubbleSizeLabel" type="String" default="Bubble Size" description="Label for the bubble size on the tooltip"/>
<!-- Donut CHART SPECIFIC ATTRIBUTES -->
<aura:attribute name="segmentLabel" type="String" default="Label" description="Label that appears on the tooltip for the label section."/>
<aura:attribute name="valueLabel" type="String" default="Value" description="Label that appears on the tooltip for the value section"/>
<!-- GAUGE SPECIFIC ATTRIBUTES -->
<aura:attribute name="lowLabel" type="String" default="Low" description="Tooltip text of the low section in gauge type chart."/>
<aura:attribute name="medLabel" type="String" default="Medium" description="Tooltip text of the mid section in gauge type chart."/>
<aura:attribute name="highLabel" type="String" default="High" description="Tooltip text of the high section in gauge type chart."/>
<!-- Rerender the chart if any attribute is changed -->
<aura:handler name="change" value="{!v.type}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.data}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.title}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.leftSubtitle}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.rightSubtitle}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.xAxisLabel}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.yAxisLabel}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.xAxisDataType}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.yAxisDataType}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.thresholdLabel}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.thresholdValue}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.orientation}" action="{!c.reRenderCharts}"/>
<!-- Not Used -->
<aura:handler name="change" value="{!v.pieDonutTooltipLabelText}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.pieDonutTooltipValueText}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.lowLabel}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.medLabel}" action="{!c.reRenderCharts}"/>
<aura:handler name="change" value="{!v.highLabel}" action="{!c.reRenderCharts}"/>
<div class="slds-box" style="border:none">
<div aura:id="chartContainer" class="sc-position--relative">
<!-- Displays the tooltip while we hover a bar graph in the chart -->
<div aura:id="tooltipContainer" style="{!'display: ' + v.tooltipDisplay + '; position: absolute; top: ' + v.tooltipYPos + 'px; left: ' + v.tooltipXPos + 'px;'}" class="sc-tooltip">
<div class="label">
<aura:unescapedHtml value="{!v.tooltipHtml}"/>
</div>
</div>
<!-- Displays the left axis or the Y axis of the bar graph -->
<div aura:id="leftAxis" class="{!'slds-truncate ' + if(v.displayAxis == true, '', 'slds-hide')}" style="{!'text-align: center; position: absolute; top: 50%; left: -40px; font-size: 0.8125em; color: rgb(139, 139, 139); transform-origin: left top 0px; transform: rotate(-90deg) translateX(-50%); max-width: ' + v.yAxisLabelMaxHeight+ 'px;'}">
{!v.yAxisLabel}
</div>
<!--Make a copy to calculate width. Can't use the original since it flickers in firefox-->
<div aura:id="tooltipContainerCopy" class="sc-tooltip sc-hidden">
<div class="label">
<aura:unescapedHtml value="{!v.tooltipHtml}"/>
</div>
</div>
<!-- Title of the chart -->
<div style="{!'font-size:' + v.fontSize + ';font-weight:bold;color: rgb(139, 139, 139);'}" class="slds-p-bottom--small slds-text-align--center slds-text-heading--small slds-truncate">
{!v.title}
</div>
<div aura:id="chart" class="chart-class" />
<!-- If you want to display the label of the axis then the below div is displayed -->
<aura:if isTrue="{!v.displayAxis}">
<div aura:id="rightAxis" class="{!'slds-truncate ' + if(v.displayAxis == true, '', 'slds-hide')}" style="text-align: center; font-size: 0.8125em; color: rgb(139, 139, 139); max-width: 70%; margin: auto; padding-top: 40px">
{!v.xAxisLabel}
</div>
</aura:if>
<!-- If the subtitles of the chart component are not empty then display the below div -->
<aura:if isTrue="{!not(and(empty(v.leftSubtitle), empty(v.rightSubtitle)))}">
<div class="slds-grid slds-p-top--small slds-text-body--small" style="{!'font-size: ' + v.fontSize + ';'}">
<div title="{!v.leftSubtitle}" class="slds-col slds-size--1-of-2 slds-truncate">{!v.leftSubtitle}</div>
<div title="{!v.rightSubtitle}" class="slds-col slds-size--1-of-2 slds-text-align--right slds-truncate">{!v.rightSubtitle}</div>
</div>
</aura:if>
</div>
</div>
</aura:component>
.THIS .sc-axis-label {
color: #bfbebe;
}
.THIS .sc-axis-value {
color: white;
font-size: 13px;
}
.THIS .sc-tooltip {
background: #2c2b2b;
box-shadow: 0 0 5px #999999;
color: white;
font-size: 12px;
padding: 10px;
position: absolute;
text-align: left;
z-index: 10;
}
.THIS .sc-threshold-line {
fill: none;
stroke: #c23934;
stroke-width: 0.5px;
stroke-dasharray: 5 5;
pointer-events: none;
}
.THIS .sc-threshold-text {
fill: #ffffff;
pointer-events: none;
}
.THIS .sc-threshold-background {
fill: #c23934;
pointer-events: none;
}
.THIS .sc-threshold-triangle {
fill: #c23934;
pointer-events: none;
}
.THIS .sc-axis path,
.THIS .sc-axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.THIS .sc-axis-text {
font: 10px sans-serif;
fill: #4d4d4d;
}
.THIS .sc-section:hover {
fill: #4682b4;
}
/* LINE CHART */
.THIS .sc-line {
fill: none;
stroke: #00a1e0;
stroke-width: 2px;
}
.THIS .sc-lc-area {
fill: #e6f6fc;
stroke: none;
}
.THIS .sc-lc-focus-line {
fill: none;
stroke: #00a1e0;
stroke-width: 0.5px;
}
.THIS .sc-lc-focus-circle {
fill: red;
}
/* /LINE CHART */
/* BAR CHART */
.THIS .sc-bc-bar {
/*fill: #00a1e0;*/
}
.THIS .sc-bc-bar:hover {
fill: #005170 !important;
}
/* /BAR CHART */
/* BUBBLE CHART */
.THIS .sc-bc-bubble {
fill-opacity: 0.3;
}
.THIS .sc-bc-bubble:hover {
fill-opacity: 0.7;
}
/* /BAR BUBBLE */
/* PIE DONUT CHART */
.THIS .sc-pdc-section-percent {
color: #c6c663;
font-size: 12px;
}
.THIS .sc-pdc-legend {
font-size: 12px;
letter-spacing: .0625rem;
}
.THIS rect {
stroke-width: 2;
}
.THIS .sc-position--static {
position: static;
}
.THIS .sc-position--relative {
position: relative;
}
.THIS .sc-position--absolute {
position: absolute;
}
.THIS .sc-position--fixed {
position: fixed;
}
.THIS .sc-position--relative {
position: relative;
}
.THIS .sc-position--fixed {
position: fixed;
}
.THIS .sc-position--static {
position: static;
}
.THIS .sc-position--absolute {
position: absolute;
}
.THIS .sc-hidden {
visibility: hidden;
opacity: 0;
position: fixed;
z-index: -9999;
left: -999999px;
left: -999vw;
pointer-events: none;
}
.THIS .chart-class svg {
overflow: visible !important;
}
/* /PIE DONUT CHART */
({
//Function called on Init
onInit: function (component, event, helper) {
var data = component.get("v.data");
console.log("The data is:::"+JSON.stringify(data));
component.set('v.scriptsLoaded', true);
component.set('v.triggerRedraw', !component.get('v.triggerRedraw'));
component.resize = $A.getCallback(function () {
if (component.isValid()) {
component.set('v.fontSize', helper.determineFontSize(component.get('v.containerWidth')));
component.set('v.chartRendered', false);
component.set('v.triggerRedraw', !component.get('v.triggerRedraw'));
} else {
window.removeEventListener('resize', component.resize);
}
});
window.addEventListener('resize', component.resize, true);
},
//Function which sets the spinner to false after chart is loaded
reRenderCharts: function (component, event, helper) {
component.set('v.chartRendered', false);
}
})
({
//Function which draws the bar chart
barChart: function (component, helper) {
component.set('v.displayAxis', true);
var dataset = component.get("v.data"); //The data retrieved from the controller or parent component
var color = helper.getColors(); //Setting the colors to the bars
//Setting the orientation of the chart as vertical, can also be turned to horizontal
var isVertical = component.get('v.orientation') === 'vertical';
var numericalAxis = isVertical ? 'y' : 'x';
var stringAxis = isVertical ? 'x' : 'y';
//Setting the labels for the X & Y axis
var xAxisLabel = component.get('v.xAxisLabel');
var yAxisLabel = component.get('v.yAxisLabel');
//Setting the height and width of the chart
var chartWidth = component.get('v.containerWidth');
var chartHeight = helper.getHeight(chartWidth);
helper.setyAxisLabelMaxHeight(component, chartHeight);
var paddingBox = helper.getPaddingBox(chartWidth);
var leftAxis = component.find('leftAxis').getElement();
$A.util.removeClass(leftAxis, 'slds-hide');
var leftAxisLabelWidth = leftAxis.clientHeight; //get height since we rotated
chartWidth -= leftAxisLabelWidth;
var x0 = d3.scaleBand().rangeRound([0, chartWidth]).paddingInner(0.1);
var x1 = d3.scaleBand().padding(0.05);
var y = d3.scaleLinear().rangeRound([chartHeight, 0]);
//Setting the formats of the numbers and the strings
var numericalFormat = function (d) {
return helper.abbreviateNumber(d)
};
var stringFormat = function (d) {
return d;
};
var xTickFormatter = stringFormat;
var yTickFormatter = numericalFormat;
//Creating the values on both the axis
var xAxis = helper.addBottomAxis(x0, 0, xTickFormatter);
var yAxis = helper.addLeftAxis(y, 0, yTickFormatter);
var svg = d3.select(component.find('chart').getElement()).append("svg")
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr('style', 'transform: translateX(' + (leftAxisLabelWidth) + 'px);');
//Here we pass the chart related data from the LCMP_CaseTrackerCharts component as a wrapper object
//The options variable below stores all the fields from the wrapper except the label/Account field
var options = d3.keys(dataset[0]).filter(function(key) { return key !== "label"; });
//Looping through each element of the data from parent and getting its name and corresponding value
dataset.forEach(function(d) {
d.valores = options.map(function(name) { return {name: name, value: +d[name]}; });
});
//X0 is the chart element, X1 is each bar in the chart
x0.domain(dataset.map(function(d) { return d.label; }));
x1.domain(options).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(dataset, function(d) { return d3.max(d.valores, function(d) { return d.value; }); })]);
//The functions which create the html to form the bar chart
svg.append("g")
.attr("class", "x sc-axis")
.attr("transform", "translate(0," + chartHeight + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y sc-axis")
.call(yAxis);
//Drawing each bar and drawing it as per its label and positioning it appropriately
var bar = svg.selectAll(".sc-bc-bar")
.data(dataset)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.label) + ",0)"; });
//Settig the x position and v position of each bar relative to the previous bar
bar.selectAll(".sc-bc-bar")
.data(function(d) { return d.valores; })
.enter().append("rect").attr('class', 'sc-bc-bar')// drawing a rectangle and appending required styling
.attr("width", x1.bandwidth()) // setting the width of each bar
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return chartHeight - y(d.value); }) // setting the height of the chart
.style("fill", function(d) { return color(d.name); }); // filling the color for the bar
//While hovering over each bar, the corresponding data values are displayed in the tooltip
bar.on('mouseover', $A.getCallback(function (dataPoint) {
var x = dataPoint.label;
var elements = document.querySelectorAll(':hover');
var l = elements.length;
l = l-1;
var elementData = elements[l].__data__;
var elementLabel = elementData.name;
if(elementLabel==="OpenCases"){
elementLabel = "Open Cases";
}
else if(elementLabel==="ClosedCases"){
elementLabel = "Closed Cases";
}
else if(elementLabel==="TotalCases"){
elementLabel = "Total Cases";
}
var y = elementLabel +" - "+elementData.value;
// Setting the format of the tooltip
var tooltipHtml = '<span class="sc-axis-label">' + xAxisLabel + ': </span><span class="sc-axis-value">' + x + '</span><br/>' +
'<span class="sc-axis-label">' + yAxisLabel + ': </span><span class="sc-axis-value">' + y + '</span>';
component.set('v.tooltipHtml', tooltipHtml)
}));
//Hide tooltip after the mouse moves out
bar.on('mouseout', $A.getCallback(function () {
helper.hideTooltip(component);
}));
//Get the position of the mouse relative to the chart
bar.on('mousemove', $A.getCallback(function (dataPoint) {
var mousePos = d3.mouse(component.find('chartContainer').getElement());
var tooltipOptions = {
x: mousePos[0],
y: mousePos[1],
chartWidth: chartWidth
}
helper.showToolTip(component, tooltipOptions);
}));
//Setting the legend for the chart (legend is the indicator which helps to determine which color represents what number)
var legend = svg.selectAll(".legend")
.data(options.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//Creating the rectangle for the legend
legend.append("rect")
.attr("x", chartWidth + 76)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
//Creating the text for the legend
legend.append("text")
.attr("x", chartWidth + 70)
.attr("y", 9)
.attr("dy", ".35em")
.attr("style", "font-size: .625rem;color: #8b8b8b;text-anchor: end")
.text(function(d) {
if(d === "OpenCases") {
d = "Open Cases";
} else if(d === "ClosedCases") {
d = "Closed Cases";
}
else if(d === "TotalCases") {
d = "Total Cases";
}
return d;
});
},
//Sets the maximum value for the Y axis
setyAxisLabelMaxHeight: function (component, chartHeight) {
component.set('v.yAxisLabelMaxHeight', chartHeight * .7);
},
// Funtion to hide the tooltip box
hideTooltip: function(component) {
component.set('v.tooltipOpacity', 0);
component.set('v.tooltipDisplay', 'none')
},
//Function to set the tooltip position and data
showToolTip: function(component, tooltipOptions) {
var tooltipElement = component.find('tooltipContainer').getElement()
var tooltipElementCopy = component.find('tooltipContainerCopy').getElement();
var tooltipOffSet = 10;
var tooltipXPos = tooltipOptions.x + tooltipOffSet;
var tooltipYPos = tooltipOptions.y + tooltipOffSet;
if ((tooltipElementCopy.clientWidth + tooltipXPos) > tooltipOptions.chartWidth) {
tooltipXPos -= (tooltipElementCopy.clientWidth + (tooltipOffSet * 2));
if (tooltipXPos < 0) {
tooltipXPos = tooltipOptions.x + tooltipOffSet;
}
}
component.set('v.tooltipDisplay', 'block');
component.set('v.tooltipXPos', tooltipXPos);
component.set('v.tooltipYPos', tooltipYPos);
component.set('v.tooltipOpacity', 1);
},
//Function which sets the padding for the chart
getPaddingBox: function(chartWidth) {
return {
top: chartWidth * .02,
left: chartWidth * .1,
bottom: chartWidth * .1,
right: chartWidth * .02
}
},
//Function which sets the colors for the bars
getColors: function() {
return d3.scaleOrdinal().range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c",
"#ff8c00", "#8cc5aa", "#9fa0a4", "#16325c", "#76ded9", "#08a69e",
"#e2cd81", "#e49e24", "#c03a38"]);
},
//Function which rounds off the bar value
abbreviateNumber: function(amount) {
var absAmount = Math.abs(Number(amount));
var amountNumber = Number(amount);
var shortenedNumber = amountNumber;
var abbreviation = '';
var trillion = Math.pow(10, 12);
var billion = Math.pow(10, 9);
var million = Math.pow(10, 6);
var thousand = Math.pow(10, 3);
if (absAmount / trillion >= 1) {
shortenedNumber = amountNumber / trillion;
abbreviation = 'T';
} else if (absAmount / billion >= 1) {
shortenedNumber = amountNumber / billion;
abbreviation = 'B';
} else if (absAmount / million >= 1) {
shortenedNumber = amountNumber / million;
abbreviation = 'M';
} else if (absAmount / thousand >= 1) {
shortenedNumber = amountNumber / thousand;
abbreviation = 'K';
}
return (parseFloat(shortenedNumber.toFixed(1)) + abbreviation);
},
addLeftAxis: function(scale, tickSizeOuter, tickFormat) {
return d3.axisLeft().scale(scale).tickSizeOuter(tickSizeOuter).tickFormat(tickFormat);
},
addBottomAxis: function(scale, tickSizeOuter, tickFormat) {
return d3.axisBottom().scale(scale).tickSizeOuter(tickSizeOuter).tickFormat(tickFormat);
},
getWidth: function(basedOnWidth) {
return basedOnWidth * .75;
},
getHeight: function(basedOnWidth) {
return basedOnWidth * .66;
},
//Function to determine the font size of the chart based on chart width
determineFontSize: function(chartWidth) {
var fontSize = '.8125rem';
if (chartWidth < 767) {
fontSize = '.625rem';
} else if (chartWidth < 1023) {
fontSize = '.75rem';
}
return fontSize;
},
//Function which performs the drawing of the chart
drawChart: function(component, helper) {
var chartType = component.get('v.type');
var chartToDraw = chartType + 'Chart';
if (chartType === 'pie' || chartType === 'donut') chartToDraw = 'pieDonutChart';
helper[chartToDraw](component, helper);
component.set('v.chartRendered', true);
}
})
({
rerender: function (component, helper) {
this.superRerender();
if (!component.get('v.scriptsLoaded') || component.get('v.chartRendered')) {
return;
}
//make sure threshold isn't a string
component.get('v.thresholdValue', parseInt(component.get('v.thresholdValue')));
var containerWidth = component.find('chartContainer').getElement().clientWidth;
component.set('v.containerWidth', containerWidth);
var chartDiv = component.find('chart').getElement();
chartDiv.innerHTML = '';
helper.drawChart(component, helper);
},
unrender: function(component) {
this.superUnrender();
window.removeEventListener('resize', component.resize);
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment