数据:
data: [
{ month: '2019-1-1', apples: 3840, bananas: 1920, cherries: 960, oranges: 300 },
{ month: '2019-1-2', apples: 1600, bananas: 1440, cherries: 960, oranges: 400 },
{ month: '2019-1-3', apples: 640, bananas: 960, cherries: 640, oranges: 200 },
{ month: '2019-1-4', apples: 320, bananas: 480, cherries: 640, oranges: 500 },
],
import * as d3 from 'd3';
export default function stackRect(id, data) {
(() => {
d3.select(id)
.selectAll('svg')
.remove();
})();
const height = 800;
const width = 800;
const margin = 20;
const padding = {
left: 100,
right: 100,
top: 100,
bottom: 100,
};
const axisHeight = height - padding.top - padding.bottom;
const axisWidth = width - padding.left - padding.right;
const colorZ = d3.scaleOrdinal(d3.schemePaired);
// 数据转换器
const stack = d3
.stack()
.keys(['apples', 'bananas', 'cherries', 'oranges'])
.order(d3.stackOrderNone) // 使用原始数据的顺序不进行顺序调整
.offset(d3.stackOffsetNone);// 堆栈偏移-零基线
const stackData = stack(data);
// x与y比例尺
const xScale = d3
.scaleBand()
.range([0, axisWidth])
.domain(data.map(d => d.month));
const yScale = d3
.scaleLinear()
.range([axisHeight, 0])
.domain([0, d3.max(stackData[stackData.length - 1], item => item[1])]);
// x轴和y轴
const xAxis = d3.axisBottom().scale(xScale);
const yAxis = d3.axisLeft(yScale);
// 画布
const svg = d3
.select(id)
.append('svg')
.attr('width', width)
.attr('height', height);
// 添加x轴
svg
.append('g')
.attr('class', 'axis')
.attr('transform', `translate(${padding.left},${height - padding.bottom})`)
.call(xAxis);
// 添加y轴
svg
.append('g')
.attr('class', 'axis')
.attr('transform', `translate(${padding.left},${padding.top})`)
.call(yAxis);
// 将二维数组的第一维剥离,打散成n列
const rectContainer = svg
.selectAll('rectContainer')
.data(stackData)
.enter()
.append('g')
.attr('class', 'rectContainer')
.attr('fill', d => colorZ(d.key));
// 渲染每一列
rectContainer
.selectAll('rect')
.data(d => d)
.enter()
.append('rect')
.attr('x', d => {
return xScale(d.data.month) + padding.left + margin / 2;
})
.attr('y', d => {
return yScale(d[1]) + padding.top;
})
.attr('width', () => {
return xScale.bandwidth() - margin;
})
.attr('height', d => {
return height - padding.top - padding.bottom - yScale(d[1] - d[0]);
})
.attr('stroke', '#ccc');
// 添加图例
svg
.selectAll('circle')
.data(['apples', 'bananas', 'cherries', 'oranges'])
.enter()
.append('circle')
.attr('cx', () => {
return width - padding.right - 80;
})
.attr('cy', (d, i) => {
return padding.top + 25 * i;
})
.attr('r', '6')
.attr('fill', d => {
return colorZ(d);
});
const texts = svg
.selectAll('textContainer')
.data(['apples', 'bananas', 'cherries', 'oranges'])
.enter()
.append('g')
.attr('class', 'textContainer');
texts
.append('text')
.attr('x', () => {
return width - padding.right - 60;
})
.attr('y', (d, i) => {
return padding.top + 25 * i;
})
.attr('dy', '0.32em')
.text(d => d)
.attr('fill', d => colorZ(d));
}