/* ------------------------------------------------------------------------------ * * # 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 ''; }); // 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 // ------------------------------ // 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) var nested = {'val1': formatted} console.log(nested) // 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['val1']; // 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('%m/%d')); // 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(); });