Bar Chart
d3.scaleBand() maps category names to X positions; d3.scaleLinear() maps values to bar heights. Bars start at height 0 and transition to full height via selection.transition().duration(600). Tooltips use position: fixed and update position on mousemove.
d3.scaleBand()은 카테고리 이름을 X 좌표에, d3.scaleLinear()은 값을 막대 높이에 매핑합니다. 막대는 높이 0에서 시작해 selection.transition().duration(600)으로 최종 높이로 전환됩니다. 툴팁은 position: fixed를 사용하며 mousemove로 위치를 업데이트합니다.
Source Code script.js
const data = [
{ month: 'Jan', value: 42 },
{ month: 'Feb', value: 58 },
{ month: 'Mar', value: 75 },
{ month: 'Apr', value: 63 },
{ month: 'May', value: 91 },
{ month: 'Jun', value: 84 },
{ month: 'Jul', value: 110 },
{ month: 'Aug', value: 97 },
{ month: 'Sep', value: 88 },
{ month: 'Oct', value: 120 },
{ month: 'Nov', value: 134 },
{ month: 'Dec', value: 156 },
];
const margin = { top: 20, right: 20, bottom: 40, left: 50 };
const chartEl = document.getElementById('chart');
const totalWidth = chartEl.clientWidth || 720;
const width = totalWidth - margin.left - margin.right;
const height = 360 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', 360)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand()
.domain(data.map(d => d.month))
.range([0, width])
.padding(0.3);
const maxValue = d3.max(data, d => d.value);
const y = d3.scaleLinear()
.domain([0, maxValue + 20])
.range([height, 0]);
// Horizontal grid lines
svg.append('g')
.attr('class', 'grid')
.call(
d3.axisLeft(y)
.tickSize(-width)
.tickFormat('')
);
// X axis
svg.append('g')
.attr('class', 'axis axis--x')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x).tickSizeOuter(0));
// Y axis
svg.append('g')
.attr('class', 'axis axis--y')
.call(
d3.axisLeft(y)
.ticks(6)
.tickSizeOuter(0)
.tickFormat(d => `$${d}k`)
);
// Bars — start at y=height with height=0, then transition
const bars = svg.selectAll('.bar')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('width', x.bandwidth())
.attr('y', height)
.attr('height', 0);
bars.transition()
.duration(600)
.delay((d, i) => i * 30)
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value));
// Tooltip
const tooltip = d3.select('#tooltip');
svg.selectAll('.bar')
.on('mouseover', function (event, d) {
tooltip
.style('opacity', 1)
.html(`${d.month} · $${d.value}k`);
})
.on('mousemove', function (event) {
tooltip
.style('left', `${event.pageX + 12}px`)
.style('top', `${event.pageY - 28}px`);
})
.on('mouseout', function () {
tooltip.style('opacity', 0);
});