在 D3 分组条形图中自定义网格线
Customize grid lines in D3 grouped bar chart
我是 D3 的新手,我正在尝试使用 D3 构建分组条形图。我需要在一个图结束时放置 X 轴上的网格线。目前它位于中间。此外,目前网格线出现在绘制图形的顶部,如何将网格线移动到图形后面。在 Y 轴上,是否可以让我的刻度从 1000、2000、3000、4000 开始,而不是当前显示 .5k、1k 等
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3
.select('#grouped-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1);
const x1 = d3.scaleBand().padding(0.05);
const y = d3.scaleLinear().rangeRound([height, 0]);
const z = d3.scaleOrdinal().range(['#004c6d', '#255e7e', '#3d708f']);
const keys = Object.keys(data[0]).slice(1);
x0.domain(data.map((d) => d.category.model));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, 4000]);
const gX = g
.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x0));
const gY = g
.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(y).ticks(null, 's'));
g.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom().scale(x0).tickSize(-height, 0, 0).tickFormat(''));
g.append('g')
.attr('class', 'grid')
.call(d3.axisLeft().scale(y).tickSize(-width, 0, 0).tickFormat(''));
g.append('g')
.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', (d) => 'translate(' + x0(d.category.model) + ',0)')
.selectAll('rect')
.data((d) =>
keys.map((key) => {
return { key: key, value: d[key] };
})
)
.enter()
.append('rect')
.attr('x', (d) => x1(d.key))
.attr('y', (d) => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', (d) => height - y(d.value))
.attr('fill', (d) => z(d.key));
</script>
</body>
</html>
对于 y 轴刻度线,您可以将刻度数设置为 4。然后它们会在千位处绘制:d3.axisLeft(y).ticks(4, 's')
。您还可以显式设置 tickValues.
对于网格线,您可以复制轴创建的刻度线并调整它们的坐标以穿过整个图表。对于垂直网格线,您可以将它们的 x 坐标偏移 x0.step() / 2
,以便将它们放在组之间 (step() docs)。
此外,我建议使用灰色刻度线而不是黑色刻度线,这样它们就不会引起太多注意。最后,您为条形图选择的两种颜色有点难以相互区分。 Colorgorical and ColorBrewer can be useful for picking colors. Many of the ColorBrewer schemes are provided in d3-scale-chromatic.
等工具
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// data
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
// set up
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('#grouped-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// scales
const keys = Object.keys(data[0]).slice(1);
const x0 = d3.scaleBand()
.domain(data.map((d) => d.category.model))
.rangeRound([0, width])
.paddingInner(0.1);
const x1 = d3.scaleBand()
.domain(keys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.max(+d.type1, +d.type2))])
.rangeRound([height, 0]);
const z = d3.scaleOrdinal()
.domain(keys)
// colors picked with colorgorical
.range(["rgb(88,181,225)", "rgb(26,101,135)"]);
// axes
g.append('g')
.attr('class', 'axis')
// move group to bottom of chart
.attr('transform', `translate(0,${height})`)
// add axis
.call(d3.axisBottom(x0).tickSizeOuter(0))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip last tick since there's no group after it
.filter((d, i, nodes) => i < nodes.length - 1)
.clone()
.attr('stroke', '#cccccc')
// move to in between the groups
.attr('x1', x0.step() / 2)
.attr('x2', x0.step() / 2)
// make them go from top to bottom of chart
.attr('y1', -height)
.attr('y2', 0));
g.append('g')
.attr('class', 'axis')
// add axis
.call(d3.axisLeft(y).ticks(4, 's'))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip first tick since there's already a baseline from the x axis
.filter((d, i) => i > 0)
.clone()
.attr('stroke', '#cccccc')
.attr('x1', 0)
.attr('x2', width));
// bars
g.append('g')
.selectAll('g')
.data(data)
.join('g')
.attr('transform', d => `translate(${x0(d.category.model)},0)`)
.selectAll('rect')
.data(d => keys.map((key) => ({ key: key, value: d[key] })))
.join('rect')
.attr('x', d => x1(d.key))
.attr('y', d => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', d => height - y(d.value))
.attr('fill', d => z(d.key));
</script>
</body>
</html>
我是 D3 的新手,我正在尝试使用 D3 构建分组条形图。我需要在一个图结束时放置 X 轴上的网格线。目前它位于中间。此外,目前网格线出现在绘制图形的顶部,如何将网格线移动到图形后面。在 Y 轴上,是否可以让我的刻度从 1000、2000、3000、4000 开始,而不是当前显示 .5k、1k 等
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3
.select('#grouped-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1);
const x1 = d3.scaleBand().padding(0.05);
const y = d3.scaleLinear().rangeRound([height, 0]);
const z = d3.scaleOrdinal().range(['#004c6d', '#255e7e', '#3d708f']);
const keys = Object.keys(data[0]).slice(1);
x0.domain(data.map((d) => d.category.model));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, 4000]);
const gX = g
.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x0));
const gY = g
.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(y).ticks(null, 's'));
g.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom().scale(x0).tickSize(-height, 0, 0).tickFormat(''));
g.append('g')
.attr('class', 'grid')
.call(d3.axisLeft().scale(y).tickSize(-width, 0, 0).tickFormat(''));
g.append('g')
.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', (d) => 'translate(' + x0(d.category.model) + ',0)')
.selectAll('rect')
.data((d) =>
keys.map((key) => {
return { key: key, value: d[key] };
})
)
.enter()
.append('rect')
.attr('x', (d) => x1(d.key))
.attr('y', (d) => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', (d) => height - y(d.value))
.attr('fill', (d) => z(d.key));
</script>
</body>
</html>
对于 y 轴刻度线,您可以将刻度数设置为 4。然后它们会在千位处绘制:d3.axisLeft(y).ticks(4, 's')
。您还可以显式设置 tickValues.
对于网格线,您可以复制轴创建的刻度线并调整它们的坐标以穿过整个图表。对于垂直网格线,您可以将它们的 x 坐标偏移 x0.step() / 2
,以便将它们放在组之间 (step() docs)。
此外,我建议使用灰色刻度线而不是黑色刻度线,这样它们就不会引起太多注意。最后,您为条形图选择的两种颜色有点难以相互区分。 Colorgorical and ColorBrewer can be useful for picking colors. Many of the ColorBrewer schemes are provided in d3-scale-chromatic.
等工具<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="grouped-chart"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// data
const data = [
{
category: {
model: 'A',
},
type1: '1000',
type2: '2000',
},
{
category: {
model: 'B',
},
type1: '2000',
type2: '3000',
},
{
category: {
model: 'C',
},
type1: '1500',
type2: '4000',
},
];
// set up
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const width = 400 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('#grouped-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background', '#fff');
const g = svg
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// scales
const keys = Object.keys(data[0]).slice(1);
const x0 = d3.scaleBand()
.domain(data.map((d) => d.category.model))
.rangeRound([0, width])
.paddingInner(0.1);
const x1 = d3.scaleBand()
.domain(keys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.max(+d.type1, +d.type2))])
.rangeRound([height, 0]);
const z = d3.scaleOrdinal()
.domain(keys)
// colors picked with colorgorical
.range(["rgb(88,181,225)", "rgb(26,101,135)"]);
// axes
g.append('g')
.attr('class', 'axis')
// move group to bottom of chart
.attr('transform', `translate(0,${height})`)
// add axis
.call(d3.axisBottom(x0).tickSizeOuter(0))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip last tick since there's no group after it
.filter((d, i, nodes) => i < nodes.length - 1)
.clone()
.attr('stroke', '#cccccc')
// move to in between the groups
.attr('x1', x0.step() / 2)
.attr('x2', x0.step() / 2)
// make them go from top to bottom of chart
.attr('y1', -height)
.attr('y2', 0));
g.append('g')
.attr('class', 'axis')
// add axis
.call(d3.axisLeft(y).ticks(4, 's'))
// copy tick marks to create grid lines
.call(g => g.selectAll('.tick > line')
// skip first tick since there's already a baseline from the x axis
.filter((d, i) => i > 0)
.clone()
.attr('stroke', '#cccccc')
.attr('x1', 0)
.attr('x2', width));
// bars
g.append('g')
.selectAll('g')
.data(data)
.join('g')
.attr('transform', d => `translate(${x0(d.category.model)},0)`)
.selectAll('rect')
.data(d => keys.map((key) => ({ key: key, value: d[key] })))
.join('rect')
.attr('x', d => x1(d.key))
.attr('y', d => y(d.value))
.attr('width', x1.bandwidth())
.attr('height', d => height - y(d.value))
.attr('fill', d => z(d.key));
</script>
</body>
</html>