|
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 |
|
}); |
|
} |