123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- /* ------------------------------------------------------------------------------
- *
- * # 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 '<ul class="list-unstyled mb-1">' +
- '<li>' + '<div class="font-size-base my-1"><i class="icon-circle-left2 mr-2"></i>' + d.name + ' app' + '</div>' + '</li>' +
- '<li>' + 'Sales: ' + '<span class="font-weight-semibold float-right">' + d.value + '</span>' + '</li>' +
- '<li>' + 'Revenue: ' + '<span class="font-weight-semibold float-right">' + '$' + (d.value * 25).toFixed(2) + '</span>' + '</li>' +
- '</ul>';
- });
- // 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 '<ul class="list-unstyled mb-1">' +
- '<li>' + '<div class="font-size-base my-1"><i class="icon-check2 mr-2"></i>' + formatDate(d.date) + '</div>' + '</li>' +
- '<li>' + 'Sales: ' + '<span class="font-weight-semibold float-right">' + d.alpha + '</span>' + '</li>' +
- '<li>' + 'Revenue: ' + '<span class="font-weight-semibold float-right">' + '$' + (d.alpha * 25).toFixed(2) + '</span>' + '</li>' +
- '</ul>';
- });
- // 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();
- });
|