// 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