const margin = { top: 10, right: 10, bottom: 25, left: 35 }; const maxWidth = 5120; const maxHeight = 2880; const xVariable = 'letter'; const yVariable = 'frequency'; const xValue = (d) => d[xVariable]; const yValue = (d) => d[yVariable]; const faintGray = '#ededed'; const xScale = d3.scaleBand(); const yScale = d3.scaleLinear(); const xAxis = d3.axisBottom().scale(xScale); const yAxis = d3.axisLeft().scale(yScale); const layers = defineLayers(); let data; // main W.addListener(render); d3.csv('data.csv', rowParser, (error, response) => { updateData(response); render(); }); // main function defineLayers() { const svg = d3.select('.container').append('svg') .attrs({ width: '100%', height: '100%' }); const chartArea = svg.append('g') .classed('chartArea', true) .attr('transform', `translate(${margin.left}, ${margin.top})`); const barsGroup = chartArea.append('g') .classed('bars', true); const xAxisG = chartArea.append('g') .classed('axis', true) .classed('x', true); const yAxisG = chartArea.append('g') .classed('axis', true) .classed('y', true); return { svg, chartArea, barsGroup, xAxisG, yAxisG }; } function rowParser(d) { // coerce to a Number from a String (or anything) d[yVariable] = Number(d[yVariable]); return d; } function getContainerDimensions() { const container = d3.select('.container') return { width: parseFloat(container.style('width')), height: parseFloat(container.style('height')) } } function updateData(newData) { data = newData; xScale.domain(data.map(xValue)); yScale.domain([0, d3.max(data.map(yValue))]); } function render() { updateScales(); renderAxes(); renderBars(); } function updateScales() { const dimensions = getContainerDimensions() const newWidth = d3.min([dimensions.width, maxWidth]) - margin.left - margin.right; const newHeight = d3.min([dimensions.height, maxHeight]) - margin.top - margin.bottom; xScale .range([0, newWidth]) .paddingInner(0.1) .bandwidth(10); yScale.range([newHeight, 0]); } function renderAxes() { const dimensions = getContainerDimensions() const newHeight = d3.min([dimensions.height, maxHeight]); layers.xAxisG .transition().duration(150) .attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`) .call(xAxis); layers.yAxisG .transition().duration(150) .call(yAxis); // style the axes d3.selectAll('.axis text') .styles({ 'font-family': 'sans-serif', 'font-size': '10px' }); d3.selectAll('.axis path') .styles({ fill: 'none', stroke: '#161616' }); d3.selectAll('.axis line') .styles({'stroke': 'black'}); } function renderBars() { const updateSelection = layers.barsGroup.selectAll('g').data(data); const enterSelection = updateSelection.enter().append('g') const mergeSelection = updateSelection.merge(enterSelection); const exitSelection = updateSelection.exit(); enterSelection .classed('bar', true) .style('fill', faintGray) .on('mouseover', onBarActive) .on('mouseout', onBarInactive) ; enterSelection.append('rect') enterSelection.append('text') .text((d,i) => `${d.letter} is number ${i} with frequency ${d.frequency *100}%`) mergeSelection .transition().duration(150) .selectAll('rect') .attrs({ x: (d) => xScale(xValue(d)), width: xScale.bandwidth, y: (d) => yScale(yValue(d)), height: (d) => yScale(0) - yScale(yValue(d)) }); exitSelection .on('mouseover', null) .on('mouseout', null) .remove(); } function onBarActive() { d3.select(this) .styles({ 'fill': 'steelblue', 'fill-opacity': 0.6 }); } function onBarInactive() { d3.select(this) .styles({ 'fill': faintGray, 'fill-opacity': 1 }); }