/* ------------------------------------------------------------------------------
*
* # D3.js - horizontal bar chart
*
* Demo d3.js horizontal bar chart setup with .csv data source
*
* ---------------------------------------------------------------------------- */
// Setup module
// ------------------------------
var DashboardLines = function() {
//
// Setup module components
//
// App sales line chart
var _AppSalesLinesChart = function(element, height) {
if (typeof d3 == 'undefined' || typeof d3.tip == 'undefined') {
console.warn('Warning - d3.min.js is not loaded.');
return;
}
// Initialize chart only if element exsists in the DOM
if($(element).length > 0) {
// Basic setup
// ------------------------------
// Define main variables
var d3Container = d3.select(element),
margin = {top: 5, right: 30, bottom: 30, left: 50},
width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// Tooltip
var tooltip = d3.tip()
.attr('class', 'd3-tip')
.html(function (d) {
return '
' +
'- ' + '
' + d.name + ' app' + '
' + ' ' +
'- ' + 'Sales: ' + '' + d.value + '' + '
' +
'- ' + 'Revenue: ' + '' + '$' + (d.value * 25).toFixed(2) + '' + '
' +
'
';
});
// Format date
var parseDate = d3.time.format('%Y/%m/%d').parse,
formatDate = d3.time.format('%b %d, %y');
// Line colors
var scale = ['#4CAF50', '#FF5722', '#5C6BC0'],
color = d3.scale.ordinal().range(scale);
// Create chart
// ------------------------------
// Container
var container = d3Container.append('svg');
// SVG element
var svg = container
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(tooltip);
// Add date range switcher
// ------------------------------
var menu = document.getElementById('select_date');
menu.addEventListener('change', change);
// Load data
// ------------------------------
d3.csv('../../../../global_assets/demo_data/dashboard/app_sales.csv', function(error, data) {
formatted = data;
redraw();
});
// Construct layout
// ------------------------------
// Add events
var altKey;
d3.select(window)
.on('keydown', function() { altKey = d3.event.altKey; })
.on('keyup', function() { altKey = false; });
// Set terms of transition on date change
function change() {
d3.transition()
.duration(altKey ? 7500 : 500)
.each(redraw);
}
// Main chart drawing function
// ------------------------------
function redraw() {
// Construct chart layout
// ------------------------------
// Create data nests
var nested = d3.nest()
.key(function(d) { return d.type; })
.map(formatted)
// Get value from menu selection
// the option values correspond
//to the [type] value we used to nest the data
var series = menu.value;
// Only retrieve data from the selected series using nest
var data = nested[series];
// For object constancy we will need to set 'keys', one for each type of data (column name) exclude all others.
color.domain(d3.keys(data[0]).filter(function(key) { return (key !== 'date' && key !== 'type'); }));
// Setting up color map
var linedata = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {name: name, date: parseDate(d.date), value: parseFloat(d[name], 10)};
})
};
});
// Draw the line
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); })
.interpolate('cardinal');
// Construct scales
// ------------------------------
// Horizontal
var x = d3.time.scale()
.domain([
d3.min(linedata, function(c) { return d3.min(c.values, function(v) { return v.date; }); }),
d3.max(linedata, function(c) { return d3.max(c.values, function(v) { return v.date; }); })
])
.range([0, width]);
// Vertical
var y = d3.scale.linear()
.domain([
d3.min(linedata, function(c) { return d3.min(c.values, function(v) { return v.value; }); }),
d3.max(linedata, function(c) { return d3.max(c.values, function(v) { return v.value; }); })
])
.range([height, 0]);
// Create axes
// ------------------------------
// Horizontal
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickPadding(8)
.ticks(d3.time.days)
.innerTickSize(4)
.tickFormat(d3.time.format('%a')); // Display hours and minutes in 24h format
// Vertical
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(6)
.tickSize(0 -width)
.tickPadding(8);
//
// Append chart elements
//
// Append axes
// ------------------------------
// Horizontal
svg.append('g')
.attr('class', 'd3-axis d3-axis-horizontal')
.attr('transform', 'translate(0,' + height + ')');
// Vertical
svg.append('g')
.attr('class', 'd3-axis d3-axis-vertical d3-axis-transparent d3-grid d3-grid-dashed');
// Append lines
// ------------------------------
// Bind the data
var lines = svg.selectAll('.app-sales-lines')
.data(linedata)
// Append a group tag for each line
var lineGroup = lines
.enter()
.append('g')
.attr('class', 'app-sales-lines')
.attr('id', function(d){ return d.name + '-line'; });
// Append the line to the graph
lineGroup.append('path')
.attr('class', 'd3-line d3-line-medium')
.style('stroke', function(d) { return color(d.name); })
.style('opacity', 0)
.attr('d', function(d) { return line(d.values[0]); })
.transition()
.duration(500)
.delay(function(d, i) { return i * 200; })
.style('opacity', 1);
// Append circles
// ------------------------------
var circles = lines.selectAll('circle')
.data(function(d) { return d.values; })
.enter()
.append('circle')
.attr('class', 'd3-line-circle d3-line-circle-medium')
.attr('cx', function(d,i){return x(d.date)})
.attr('cy',function(d,i){return y(d.value)})
.attr('r', 3)
.style('stroke', function(d) { return color(d.name); });
// Add transition
circles
.style('opacity', 0)
.transition()
.duration(500)
.delay(500)
.style('opacity', 1);
// Append tooltip
// ------------------------------
// Add tooltip on circle hover
circles
.on('mouseover', function (d) {
tooltip.offset([-15, 0]).show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
.on('mouseout', function (d) {
tooltip.hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
});
// Change tooltip direction of first point
// to always keep it inside chart, useful on mobiles
lines.each(function (d) {
d3.select(d3.select(this).selectAll('circle')[0][0])
.on('mouseover', function (d) {
tooltip.offset([0, 15]).direction('e').show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
.on('mouseout', function (d) {
tooltip.direction('n').hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
});
})
// Change tooltip direction of last point
// to always keep it inside chart, useful on mobiles
lines.each(function (d) {
d3.select(d3.select(this).selectAll('circle')[0][d3.select(this).selectAll('circle').size() - 1])
.on('mouseover', function (d) {
tooltip.offset([0, -15]).direction('w').show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
.on('mouseout', function (d) {
tooltip.direction('n').hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
})
})
// Update chart on date change
// ------------------------------
// Set variable for updating visualization
var lineUpdate = d3.transition(lines);
// Update lines
lineUpdate.select('path')
.attr('d', function(d, i) { return line(d.values); });
// Update circles
lineUpdate.selectAll('circle')
.attr('cy',function(d,i){return y(d.value)})
.attr('cx', function(d,i){return x(d.date)});
// Update vertical axes
d3.transition(svg)
.select('.d3-axis-vertical')
.call(yAxis);
// Update horizontal axes
d3.transition(svg)
.select('.d3-axis-horizontal')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// Resize chart
// ------------------------------
// Call function on window resize
window.addEventListener('resize', appSalesResize);
// Call function on sidebar width change
var sidebarToggle = document.querySelector('.sidebar-control');
sidebarToggle && sidebarToggle.addEventListener('click', appSalesResize);
// Resize function
//
// Since D3 doesn't support SVG resize by default,
// we need to manually specify parts of the graph that need to
// be updated on window resize
function appSalesResize() {
// Layout
// -------------------------
// Define width
width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
// Main svg width
container.attr('width', width + margin.left + margin.right);
// Width of appended group
svg.attr('width', width + margin.left + margin.right);
// Horizontal range
x.range([0, width]);
// Vertical range
y.range([height, 0]);
// Chart elements
// -------------------------
// Horizontal axis
svg.select('.d3-axis-horizontal').call(xAxis);
// Vertical axis
svg.select('.d3-axis-vertical').call(yAxis.tickSize(0-width));
// Lines
svg.selectAll('.d3-line').attr('d', function(d, i) { return line(d.values); });
// Circles
svg.selectAll('.d3-line-circle').attr('cx', function(d,i){return x(d.date)})
}
}
}
};
// Daily revenue line chart
var _DailyRevenueLineChart = function(element, height) {
if (typeof d3 == 'undefined') {
console.warn('Warning - d3.min.js is not loaded.');
return;
}
// Initialize chart only if element exsists in the DOM
if($(element).length > 0) {
// Basic setup
// ------------------------------
// Add data set
var dataset = [
{
'date': '04/13/14',
'alpha': '60'
}, {
'date': '04/14/14',
'alpha': '35'
}, {
'date': '04/15/14',
'alpha': '65'
}, {
'date': '04/16/14',
'alpha': '50'
}, {
'date': '04/17/14',
'alpha': '65'
}, {
'date': '04/18/14',
'alpha': '20'
}, {
'date': '04/19/14',
'alpha': '60'
}
];
// Main variables
var d3Container = d3.select(element),
margin = {top: 0, right: 0, bottom: 0, left: 0},
width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
height = height - margin.top - margin.bottom,
padding = 20;
// Format date
var parseDate = d3.time.format('%m/%d/%y').parse,
formatDate = d3.time.format('%a, %B %e');
// Colors
var lineColor = '#fff',
guideColor = 'rgba(255,255,255,0.3)';
// Add tooltip
// ------------------------------
var tooltip = d3.tip()
.attr('class', 'd3-tip')
.html(function (d) {
return '';
});
// Create chart
// ------------------------------
// Add svg element
var container = d3Container.append('svg');
// Add SVG group
var svg = container
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(tooltip);
// Load data
// ------------------------------
dataset.forEach(function (d) {
d.date = parseDate(d.date);
d.alpha = +d.alpha;
});
// Construct scales
// ------------------------------
// Horizontal
var x = d3.time.scale()
.range([padding, width - padding]);
// Vertical
var y = d3.scale.linear()
.range([height, 5]);
// Set input domains
// ------------------------------
// Horizontal
x.domain(d3.extent(dataset, function (d) {
return d.date;
}));
// Vertical
y.domain([0, d3.max(dataset, function (d) {
return Math.max(d.alpha);
})]);
// Construct chart layout
// ------------------------------
// Line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.alpha)
});
//
// Append chart elements
//
// Add mask for animation
// ------------------------------
// Add clip path
var clip = svg.append('defs')
.append('clipPath')
.attr('id', 'clip-line-small');
// Add clip shape
var clipRect = clip.append('rect')
.attr('class', 'clip')
.attr('width', 0)
.attr('height', height);
// Animate mask
clipRect
.transition()
.duration(1000)
.ease('linear')
.attr('width', width);
// Line
// ------------------------------
// Path
var path = svg.append('path')
.attr({
'd': line(dataset),
'clip-path': 'url(#clip-line-small)',
'class': 'd3-line d3-line-medium'
})
.style('stroke', lineColor);
// Animate path
svg.select('.line-tickets')
.transition()
.duration(1000)
.ease('linear');
// Add vertical guide lines
// ------------------------------
// Bind data
var guide = svg.append('g')
.selectAll('.d3-line-guides-group')
.data(dataset);
// Append lines
guide
.enter()
.append('line')
.attr('class', 'd3-line-guides')
.attr('x1', function (d, i) {
return x(d.date);
})
.attr('y1', function (d, i) {
return height;
})
.attr('x2', function (d, i) {
return x(d.date);
})
.attr('y2', function (d, i) {
return height;
})
.style('stroke', guideColor)
.style('stroke-dasharray', '4,2')
.style('shape-rendering', 'crispEdges');
// Animate guide lines
guide
.transition()
.duration(1000)
.delay(function(d, i) { return i * 150; })
.attr('y2', function (d, i) {
return y(d.alpha);
});
// Alpha app points
// ------------------------------
// Add points
var points = svg.insert('g')
.selectAll('.d3-line-circle')
.data(dataset)
.enter()
.append('circle')
.attr('class', 'd3-line-circle d3-line-circle-medium')
.attr('cx', line.x())
.attr('cy', line.y())
.attr('r', 3)
.style('stroke', lineColor)
.style('fill', lineColor);
// Animate points on page load
points
.style('opacity', 0)
.transition()
.duration(250)
.ease('linear')
.delay(1000)
.style('opacity', 1);
// Add user interaction
points
.on('mouseover', function (d) {
tooltip.offset([-10, 0]).show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
// Hide tooltip
.on('mouseout', function (d) {
tooltip.hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
});
// Change tooltip direction of first point
d3.select(points[0][0])
.on('mouseover', function (d) {
tooltip.offset([0, 10]).direction('e').show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
.on('mouseout', function (d) {
tooltip.direction('n').hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
});
// Change tooltip direction of last point
d3.select(points[0][points.size() - 1])
.on('mouseover', function (d) {
tooltip.offset([0, -10]).direction('w').show(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 4);
})
.on('mouseout', function (d) {
tooltip.direction('n').hide(d);
// Animate circle radius
d3.select(this).transition().duration(250).attr('r', 3);
})
// Resize chart
// ------------------------------
// Call function on window resize
window.addEventListener('resize', revenueResize);
// Call function on sidebar width change
var sidebarToggle = document.querySelector('.sidebar-control');
sidebarToggle && sidebarToggle.addEventListener('click', revenueResize);
// Resize function
//
// Since D3 doesn't support SVG resize by default,
// we need to manually specify parts of the graph that need to
// be updated on window resize
function revenueResize() {
// Layout variables
width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
// Layout
// -------------------------
// Main svg width
container.attr('width', width + margin.left + margin.right);
// Width of appended group
svg.attr('width', width + margin.left + margin.right);
// Horizontal range
x.range([padding, width - padding]);
// Chart elements
// -------------------------
// Mask
clipRect.attr('width', width);
// Line path
svg.selectAll('.d3-line').attr('d', line(dataset));
// Circles
svg.selectAll('.d3-line-circle').attr('cx', line.x());
// Guide lines
svg.selectAll('.d3-line-guides')
.attr('x1', function (d, i) {
return x(d.date);
})
.attr('x2', function (d, i) {
return x(d.date);
});
}
}
};
//
// Return objects assigned to module
//
return {
init: function() {
_AppSalesLinesChart('#app_sales', 255);
_DailyRevenueLineChart('#today-revenue', 50);
}
}
}();
// Initialize module
// ------------------------------
document.addEventListener('DOMContentLoaded', function() {
DashboardLines.init();
});