如何在使用 D3.js 放大时删除非整数刻度?
How can I remove non-integer ticks while zooming in with D3.js?
问题:我正在尝试创建一个 pannable/zoomable 线性 X 轴,每个整数上都有刻度,从 0 到预定义的大数字(比如 1000,但可能更高)。显然这不会全部适合屏幕并且可读,所以我需要它离开屏幕并且可以平移。从 0 到 10 开头是可以接受的。到目前为止,我的解决方案存在许多问题,但第一个是如果我放大,D3 会自动在小数位的整数之间添加刻度以继续在屏幕上显示 10 个刻度。第二个是当我缩小时,范围将显示超过 1000。
快速说明:将显示的数据无关紧要。无论 1000 上是否有数据点,轴都必须从 0 到 1000,所以我无法根据我的数据集确定域。
我尝试过的解决方案:
- .ticks().这让我可以指定我想要的刻度数。然而,这似乎将所有报价都置于指定的初始范围内。如果我将范围的宽度增加到超过 svg 大小,它会展开刻度,但不是以任何可控的方式(据我所知)。此外,我 运行 涉及平移和缩放的性能问题。我发现这种行为很奇怪,因为如果我不指定刻度,它会产生一个无限的数字,我可以毫无延迟地平移。我 假设 发生延迟是因为它试图立即呈现所有 1000 个刻度,而默认 d3.js 功能会在您滚动时动态呈现它们。这似乎是一个潜在的解决方案,但我不确定如何执行它。
- .tickValues()。我可以提供一个 0 到 1000 的数组,这可以工作,但表现出与 .ticks() 完全相同的滞后行为。当我缩小时,这也不会动态地将刻度组合成 10 秒或 100 秒。
- .tickFormat()。我可以 运行 一个函数,以便将任何非整数转换为空字符串。但是,这仍然留下了刻度线。
以下是我使用最新版本 D3.js(7.3) 的代码的相关部分:
const height = 500
const width = 1200
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 35
}
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const xScale = d3.scaleLinear()
.domain([0, 10])
.range([0, innerWidth])
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', innerWidth)
.attr('height', innerHeight)
const zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([
[0, 0],
[xScale(upperBound), 0]
])
.on('zoom', zoomed)
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const xAxis = d3.axisBottom()
.scale(xScale)
const xAxisG = g.append('g')
.style('clip-path', 'url(#clip)')
.attr("class", "x-axis")
.call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`)
svg.call(zoom)
function zoomed(event) {
const updateX = event.transform.rescaleX(xScale)
const zx = xAxis.scale(updateX)
xAxisG.call(zx)
}
您可以简单地 select 容器组本身中的刻度,而不是处理轴的方法,删除 non-integers:
xAxisG.call(zx)
.selectAll(".tick")
.filter(e => e % 1)
.remove();
这里是你的代码有那个变化:
const upperBound = 1000
const height = 100
const width = 600
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 35
}
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const xScale = d3.scaleLinear()
.domain([0, 10])
.range([0, innerWidth])
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', innerWidth)
.attr('height', innerHeight)
const zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([
[0, 0],
[xScale(upperBound), 0]
])
.on('zoom', zoomed)
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const xAxis = d3.axisBottom()
.scale(xScale)
.tickFormat(d => ~~d)
const xAxisG = g.append('g')
.style('clip-path', 'url(#clip)')
.attr("class", "x-axis")
.call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`)
svg.call(zoom)
function zoomed(event) {
const updateX = event.transform.rescaleX(xScale)
const zx = xAxis.scale(updateX)
xAxisG.call(zx)
.selectAll(".tick")
.filter(e => e % 1)
.remove();
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg></svg>
问题:我正在尝试创建一个 pannable/zoomable 线性 X 轴,每个整数上都有刻度,从 0 到预定义的大数字(比如 1000,但可能更高)。显然这不会全部适合屏幕并且可读,所以我需要它离开屏幕并且可以平移。从 0 到 10 开头是可以接受的。到目前为止,我的解决方案存在许多问题,但第一个是如果我放大,D3 会自动在小数位的整数之间添加刻度以继续在屏幕上显示 10 个刻度。第二个是当我缩小时,范围将显示超过 1000。
快速说明:将显示的数据无关紧要。无论 1000 上是否有数据点,轴都必须从 0 到 1000,所以我无法根据我的数据集确定域。
我尝试过的解决方案:
- .ticks().这让我可以指定我想要的刻度数。然而,这似乎将所有报价都置于指定的初始范围内。如果我将范围的宽度增加到超过 svg 大小,它会展开刻度,但不是以任何可控的方式(据我所知)。此外,我 运行 涉及平移和缩放的性能问题。我发现这种行为很奇怪,因为如果我不指定刻度,它会产生一个无限的数字,我可以毫无延迟地平移。我 假设 发生延迟是因为它试图立即呈现所有 1000 个刻度,而默认 d3.js 功能会在您滚动时动态呈现它们。这似乎是一个潜在的解决方案,但我不确定如何执行它。
- .tickValues()。我可以提供一个 0 到 1000 的数组,这可以工作,但表现出与 .ticks() 完全相同的滞后行为。当我缩小时,这也不会动态地将刻度组合成 10 秒或 100 秒。
- .tickFormat()。我可以 运行 一个函数,以便将任何非整数转换为空字符串。但是,这仍然留下了刻度线。
以下是我使用最新版本 D3.js(7.3) 的代码的相关部分:
const height = 500
const width = 1200
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 35
}
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const xScale = d3.scaleLinear()
.domain([0, 10])
.range([0, innerWidth])
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', innerWidth)
.attr('height', innerHeight)
const zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([
[0, 0],
[xScale(upperBound), 0]
])
.on('zoom', zoomed)
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const xAxis = d3.axisBottom()
.scale(xScale)
const xAxisG = g.append('g')
.style('clip-path', 'url(#clip)')
.attr("class", "x-axis")
.call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`)
svg.call(zoom)
function zoomed(event) {
const updateX = event.transform.rescaleX(xScale)
const zx = xAxis.scale(updateX)
xAxisG.call(zx)
}
您可以简单地 select 容器组本身中的刻度,而不是处理轴的方法,删除 non-integers:
xAxisG.call(zx)
.selectAll(".tick")
.filter(e => e % 1)
.remove();
这里是你的代码有那个变化:
const upperBound = 1000
const height = 100
const width = 600
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 35
}
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const xScale = d3.scaleLinear()
.domain([0, 10])
.range([0, innerWidth])
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', innerWidth)
.attr('height', innerHeight)
const zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([
[0, 0],
[xScale(upperBound), 0]
])
.on('zoom', zoomed)
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const xAxis = d3.axisBottom()
.scale(xScale)
.tickFormat(d => ~~d)
const xAxisG = g.append('g')
.style('clip-path', 'url(#clip)')
.attr("class", "x-axis")
.call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`)
svg.call(zoom)
function zoomed(event) {
const updateX = event.transform.rescaleX(xScale)
const zx = xAxis.scale(updateX)
xAxisG.call(zx)
.selectAll(".tick")
.filter(e => e % 1)
.remove();
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg></svg>