为什么我的轴不与 d3 中的视图框对齐?

Why aren't my axes aligned with the viewbox in d3?

我正在使用视图框将数据坐标映射到 SVG 显示。

所有数据似乎都与视图框对齐(我使用红色边框显示视图框边界,使用蓝色边框显示 SVG 边界)但轴线在两个维度上都恰好偏移了 0.5。

有趣的是,刻度正确地锚定在轴应该在的位置(尽管略有偏移),只是偏移了轴线。

我尝试过旧版本的 d3(我从 3 年前将几个版本回退到 5.3.0,但所有版本都有相同的行为)

我可以看到在结果 HTML 输出中轴的 path 规范不正确,但不知道为什么 - 例如对于 y 轴,

<path class="domain" stroke="currentColor" d="M-0.2,0.5H0.5V12.5H-0.2"></path>

真的应该

<path class="domain" stroke="currentColor" d="M-0.2,0.0H0.0V12.5H-0.2"></path>

并且刻度也被 translated 错误地增加了 0.5 的额外 y 偏移量(这个不应该被转换或使用 translate(0,0)):

<g class="tick" opacity="1" transform="translate(0,0.5)"><line stroke="currentColor" x2="-0.2"></line><text fill="currentColor" x="-0.35000000000000003" dy="0.32em">0</text></g>

再现测试用例:

<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8">
<style> /* set the CSS */

body { font: 12px Arial;}

path { 
    fill: none;
}

rect {
    fill: #bbb;
    stroke: #222;
    stroke-width: 0.05;
}

.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 0.1;
    shape-rendering: crispEdges;
}

.debug {
    fill: none;
}

.debug.red {
    stroke: #f44;
}

.debug.blue {
    stroke: #44f;
}
</style>

</head><body>

<!-- load the d3.js library --> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.1/d3.min.js"></script>

<script>

var data = [[0,0], [3,3], [10,10]]
var width = 12
var height = 12

const margin = 1.5

const graph = d3.select("body")
    .append("svg")
        .attr("viewBox", d => `-${margin} -${margin} ${width+2*margin} ${height+2*margin}`)
        .attr("width", 600)

graph.append("rect")
    .attr("class", "debug blue")
    .attr("x", -margin)
    .attr("y", -margin)
    .attr("width", width + 2 * margin)
    .attr("height", height + 2 * margin)

graph.append("rect")
    .attr("class", "debug red")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height)

const scaleY = d3.scaleLinear().domain([0, height]).range([0, height])
const axisY = d3.axisLeft(scaleY).tickSize(0.2).tickPadding(0.1*margin)
const scaleX = d3.scaleLinear().domain([0, width]).range([0, width])
const axisX = d3.axisBottom(scaleX).tickSize(0.2).tickPadding(0.1*margin)

graph.append("g")
    .attr("class", "axis")
    .call(axisY)

graph.append("g")
    .attr("class", "axis")
    .attr("transform", d => `translate(0,${height})`)
    .call(axisX)

const plot = graph.selectAll("plot")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", d => { console.log(d); return d[0]})
        .attr("y", d => d[1])
        .attr("width", 0.8)
        .attr("height", 0.8)
</script>
</body></html>

JSFiddle

在 d3v4 或 5 左右,D3 开始合并轴刻度的偏移量:

Safari often botches shape-rendering: crispEdges, drawing a two-pixel-wide line even though the stroke-width is one pixel. Also, in the common case where the coordinates are rounded, using a half-pixel offset should produce crisp edges even without shape-rendering.

I think we should use zero-based indexing for coordinates, such that y = 0 means the topmost pixel and x = 0 means the leftmost pixel, and therefore offsetting by +0.5 (not -0.5) in both x and y. (source)

has caught让人猝不及防

我不确定初始实现,但从 d3v7 开始,您可以使用 axis.offset():

修改偏移量

If offset is specified, sets the offset to the specified value in pixels and returns the axis. If offset is not specified, returns the current offset which defaults to 0 on devices with a devicePixelRatio greater than 1, and 0.5px otherwise. This default offset ensures crisp edges on low-resolution devices.

所以解决方案最有可能转向d3v7并使用axis.offset(0)