javascript - 在 D3 分组条形图中自定义网格线
问题描述
我是 D3 新手,我正在尝试使用 D3 构建分组条形图。当一个图结束时,我需要在 X 轴上放置我的网格线。目前它位于中间。此外,目前网格线出现在绘制图形的顶部,如何将网格线移动到图形后面。在 Y 轴上,是否可以让我的刻度从 1000、2000、3000、4000 开始,而不是当前显示 0.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() 文档)。
另外,我会推荐灰色刻度线而不是黑色,这样它们就不会引起太多关注。最后,您为条形选择的两种颜色有点难以区分。Colorgorical和ColorBrewer等工具可用于挑选颜色。许多 ColorBrewer 方案在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>
推荐阅读
- python - 尝试运行 0x Launch Kit 向导时出现回溯错误“urlopen”
- node.js - 尝试为 clamAV 指定二进制路径
- teradatasql - 我有一个表格可以在 teradata 的列中找到逗号或管道...任何帮助将不胜感激..请建议通用查询
- postgresql - Postgres:“无法将 sql 模块加载到数据库集群中”等错误
- html - 如何在 Ion-card 上实现延迟加载
- docker - Dockerfile 中 EXPOSE 命令的目的是什么?
- java - Maven 抛出 java.lang.ClassNotFoundException: lombok.javac.handlers.HandleAccessors 即使我从项目中完全删除了 lombok?
- javascript - Jest 测试需要很长时间才能完成
- c# - RSA 加密返回奇怪的格式
- mongodb - Mongoose 不在 docker 内连接