<h2 class="bx--graph-header">Line Graph</h2>
<div class="bx--graph-container">
	<svg></svg>
	<div class="bx--tooltip" data-floating-menu-direction="top"></div>
</div>
html {
	background-color: #fff;
}

body {
	padding: 1rem 3%;
	background-color: #fff;
}

.bx--graph-header {
	font-weight: 300;
	font-size: 24px;
}

.bx--graph-overlay {
	fill: #3d70b2;
	opacity: .1;
	display: none;
	pointer-events: none;
}

.bx--line {
	stroke-width: 2;
	stroke: #00a68f;
	fill: none;
	pointer-events: none;
}

.bx--axis path {
	stroke: #5A6872;
}

.tick line {
	stroke: #5A6872;
}

.tick text {
	fill: #5A6872;
}

.bx--graph-container {
	position: relative;
}

.bx--tooltip {
	display: none;
	font-weight: 700;
	padding: .25rem .5rem;
	pointer-events: none;
}

.bx--axis--y path {
	display: none;
}

.bx--graph-label {
	font-size: 10px;
	font-weight: 700;
	fill: #5A6872;
	text-anchor: middle;
}
View Compiled
// Function to create random data in format: [date, amount]
function createData(num) {
	let data = [];
	for (var i = 0; i < num; i++) {
		const randomNum = Math.floor(Math.random() * 1000 + 1);
		let d = new Date();
		d.setDate(d.getDate() - (i * 31));
		data.push({
			date: d,
			amount: randomNum
		});
	}
	
	return data;
}

// Create + Format data
let data = createData(12).sort(function(a, b) { return a.date - b.date; });

// what are these and are they things that someone should edit
const margin = { top: 30, right: 20, bottom: 60, left: 65 };
const width = 800 - (margin.left + margin.right);
const height = 300 - (margin.top + margin.bottom);
const labelOffset = 55;
const axisOffset = 16;

// Set Time Format (JAN, FEB, etc..)
const timeFormat = d3.timeFormat('%b');

// Set the scales
const x = d3.scaleTime().range([0, width]).domain(d3.extent(data, (d) => d.date));
const y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, (d) => d.amount)]);
// const y = d3.scaleLinear().range([height, 0]).domain([0, 400]);

// Set the axes
const xAxis = d3.axisBottom()
	.scale(x)
	.tickSize(0)
	.tickFormat(timeFormat)

const yAxis = d3.axisLeft()
	.ticks(4) 
	.tickSize(-width)
	.scale(y.nice());

// Set the line
const line = d3.line()
	.x((d) => x(d.date))
	.y((d) => y(d.amount))

// Set up SVG with initial transform to avoid repeat positioning
const svg = d3.select('svg')
		.attr('class', 'bx--graph')
		.attr('width', width + (margin.left + margin.right))
		.attr('height', height + (margin.top + margin.bottom))
		.append('g')
		.attr('class', 'bx--group-container')
		.attr('transform', `translate(${margin.left}, ${margin.top})`);

// Add Y axis
svg.append('g')
	.attr('class', 'bx--axis bx--axis--y')
	.attr('stroke-dasharray', '4')
	.call(yAxis)
	.selectAll('text')
	.attr("x", -axisOffset)
	.attr('font-family', 'ibm-plex-sans');

// Add Y axis label
const yLabel = svg.select('.bx--axis--y')
	.append('text')
	.text('USAGE ($)')
	.attr('class', 'bx--graph-label')
	.attr('transform', `translate(${-labelOffset}, ${height / 2}) rotate(-90)`)
	.attr('font-family', 'ibm-plex-sans');

// Add X axis
svg.append('g')
	.attr('class', 'bx--axis bx--axis--x')
	.attr('transform', `translate(0, ${height})`)
	.call(xAxis)
	.selectAll('text')
	.attr("y", axisOffset)
	.attr('font-family', 'ibm-plex-sans');

// Add X axis label
const xLabel = svg.select('.bx--axis--x')
	.append('text')
	.text('MONTH')
	.attr('class', 'bx--graph-label')
	.attr('transform', `translate(${width / 2}, ${labelOffset})`)
	.attr('font-family', 'ibm-plex-sans');

// Add the line
const path = svg.append('g')
	.datum(data)
	.append('path')
	.attr('class', 'bx--line')
	.attr('d', line);

// Animate the line on initial draw
var totalLength = path.node().getTotalLength();
path
	.attr('stroke-dasharray', 0 + " " + totalLength)
	.transition()
	.ease(d3.easeExp)
	.duration(1000)
  .attr("stroke-dasharray", totalLength + " " + 0);

// Create Bisect function
const bisectDate = d3.bisector(function(d) {
	return d.date;
}).left;

// Create a rect to be displayed on hover
const overlay = svg.append('rect')
	.attr('class', 'bx--graph-overlay')
	.attr('height', height)

// Select Tooltip
const tooltip = d3.select('.bx--tooltip');

// Add an invisible overlay to graph area to capture mouse events
svg
	.append('rect')
	.attr('width', width)
	.attr('height', height)
	.style('fill', 'none')
	.style('pointer-events', 'all')
	.on('mouseover', () => {
		// Show the overlay + tooltip on mouseover
		overlay.style('display', 'inherit')
		tooltip.style('display', 'inherit')
	})
	.on('mouseout', () => {
		// Hide the overlay + tooltip on mouseout
		overlay.style('display', 'none')
		tooltip.style('display', 'none')
	})
	.on('mousemove', function() {
		// Get the mouse
		let mouse = d3.mouse(this);
		// Instead of passing in a date to get an x value
		// I pass in an x value (in this case, the mouse position) to get a date // not use I, but we maybe?
		let timestamp = x.invert(mouse[0]);
		// Pass the date in to the bisectDate function, which the nearest index
		let index = bisectDate(data, timestamp);
		// Get nearest x values on either side
		let start = data[index - 1];
		let end = data[index];
		// Get the width of the overlay
		let overlayWidth = x(end.date) - x(start.date);
		
		// Set the width and start position of the overlay
		overlay
			.attr('width', overlayWidth)
			.attr('x', x(start.date));
		
		// Get the tooltip width
		let tooltipWidth = tooltip.nodes()[0].getBoundingClientRect().width;
		// If overlay width is 100px, tooltip width is 50px
		// the overlay needs to be offset to the left by 25px;
		let offset = (overlayWidth - tooltipWidth) / 2;
		// Ensures the tooltip is placed over the higher amount
		let max = start.amount > end.amount ? start.amount : end.amount;
		// Determines if amount is positiove or negative 
		let sign = start.amount > end.amount ? '-' : '+';
		
		// Moves the tooltip to the correct coordinates, and adds the calculated difference.
		tooltip
				.style('left', `${x(start.date) + margin.left + offset}px`)
				.style('top', `${y(max)}px`)
				.text(`${sign} $${Math.abs(end.amount - start.amount)}`);
	});
View Compiled

External CSS

  1. https://unpkg.com/carbon-components@latest/css/carbon-components.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.3/d3.min.js
  2. https://unpkg.com/carbon-components@latest/scripts/carbon-components.js