const data = { Issue: [ 'Race Relations', 'Education', 'Terrorism', 'Energy Policy', 'Foreign Affairs', 'Environment', 'Situation in Iraq', 'Taxes', 'Healthcare Policy', 'Economy', 'Situation in Afghanistan', 'Federal Budget Deficit', 'Immigration', ], Approve: [52, 49, 48, 47, 44, 43, 41, 41, 40, 38, 36, 31, 29], Disapprove: [38, 40, 45, 42, 48, 51, 53, 54, 57, 59, 57, 64, 62], None: [10, 11, 7, 11, 8, 6, 6, 5, 3, 3, 7, 5, 9], }; const width = 500, height = 400, margin = { top: 20, right: 100, bottom: 100, left: 40 }, chartWidth = width - margin.left - margin.right, chartHeight = height - margin.top - margin.bottom; // convert to a list of { Issue, Approve, Disapprove, None } const transformedData = data.Issue.map((Issue, index) => ({ Issue, Approve: data.Approve[index], Disapprove: data.Disapprove[index], None: data.None[index], })); const answers = ['Approve', 'Disapprove', 'None']; const x = d3 .scaleBand() .paddingInner(0.2) .range([0, chartWidth]) .domain(data.Issue); const y = d3.scaleLinear().range([chartHeight, 0]).domain([0, 100]); const z = d3 .scaleOrdinal() .range(['#809ead', '#b1c0c9', '#d7d6cb']) .domain(answers); const stack = d3.stack().keys(answers)(transformedData); const svg = d3 .select('#container') .append('svg') .attr('width', width) .attr('height', height); const chart = svg .append('g') .attr('width', chartWidth) .attr('height', chartHeight) .attr('transform', `translate(${margin.left}, ${margin.top})`); // Add the X-axis const xAxis = d3.axisBottom().scale(x).tickSize(0); chart .append('g') .attr('class', 'x axis') .attr('transform', `translate(0, ${chartHeight})`) .call(xAxis) .selectAll('text') .style('text-anchor', 'end') .attr('dy', '-.35em') .attr('transform', 'translate(0, 10), rotate(-45)'); // Add the Y-axis const yAxis = d3 .axisLeft() .scale(y) .tickFormat(d => (d === 100 ? '100%' : d)) .tickSize(15); chart .append('g') .attr('class', 'y axis') .attr('transform', 'translate(-10, 0)') .call(yAxis) .selectAll('text') .attr('transform', 'translate(16, -10)'); const serieColor = d => z(d.key); const serie = chart .selectAll('.serie') .data(stack) .enter() .append('g') .attr('class', 'serie') .attr('fill', serieColor) .on('mouseover', (d, i, nodes) => d3.select(nodes[i]).attr('fill', '#555')) .on('mouseout', (d, i, nodes) => d3.select(nodes[i]).attr('fill', serieColor) ); const bar = serie .selectAll('.bar') .data((data, i, nodes) => data.map(d => { // use the parent node to get the value d.value = d.data[nodes[i].__data__.key]; return d; }) ) .enter() .append('g') .attr('class', 'bar'); bar .append('rect') .attr('x', d => x(d.data.Issue)) .attr('width', x.bandwidth()) .attr('y', d => y(d[1])) .attr('height', d => y(d[0]) - y(d[1])); bar .append('text') .attr('x', d => x(d.data.Issue) + x.bandwidth() / 2) .attr('y', d => y(d[1]) + (y(d[0]) - y(d[1])) / 2) .attr('dy', '0.35em') .attr('text-anchor', 'middle') .attr('fill', '#fff') .text(d => (d.value > 11 ? d.value : '')); bar.append('title').text(d => `${d.value}%`); const legend = serie .append('g') .attr('class', 'legend') .append('text') .attr('x', chartWidth + 8) .attr('y', d => { const last = d[d.length - 1]; return y(last[1]) + (y(last[0]) - y(last[1])) / 2; }) .attr('dy', '0.35em') .attr('fill', '#000') .text(d => d.key === 'None' ? 'No Opinion': d.key);