D3JS 的scaleTime 根据提供的域创建连续的比例。在提供日内数据时,数据集通常是不连续的。例如,在 1 分钟图表上,许多 1 分钟周期内可能没有交易。日线图上有周末和市场假期。
为了解决这个问题,我已切换到scaleLinear,但现在刻度值不一样。而且缩放功能也不一样。
我研究过 d3fc,但它看起来已经过时了,不符合我的要求。
这是我生成 xAxis 的代码:
const xScale = d3 // Create a time scale for the x-axis based on viewportBars
.scaleLinear()
.domain([0, allBars.length - 1])
.range([allBarsWidth, 0]);
// This is what I need to change
const xAxisTicks = () => {
const d = xScale.domain();
const ticks = timeTicks(d[0] as any, d[d.length - 1] as any, numTicks);
return ticks;
};
// Source input t is a unix msec timestamp
const xAxisFormat = (d: d3.NumberValue) => {
const index = Math.round(d as number);
if (index >= 0 && index < allBars.length) {
const timestamp = new Date(allBars[index].t);
return formatTick(timestamp);
}
return ''; // Return an empty string for cases where the index is out of bounds
};
// Create the x-axis generator
const xAxis = d3
.axisBottom(xScale)
.tickValues(xAxisTicks())
.tickFormat((d, _) => xAxisFormat(d));
这成功地为我的图表生成了 x 轴,但轴的显示不正确。 在scaleTime中,值按如下所述舍入:https://d3js.org/d3-scale/time
我正在尝试使用scaleLinear实现与scaleTime相同的显示、舍入和缩放功能,以解决不连续的数据。
如果您想有效处理日内数据和不连续数据集,在 D3.js 中从
scaleTime
切换到 scaleLinear
似乎是一个明智的举动,但它确实带来了一系列挑战。使用 scaleTime
,您可以利用本质上理解日期和时间的比例,确保轴刻度格式正确,四舍五入到最接近的逻辑间隔,例如一天或一个月的开始。然而,当您切换到 scaleLinear
时,您基本上放弃了内置的理解,这可能会扰乱您的刻度值和缩放功能。
所以,事情是这样的:
scaleTime
会自动调整每日图表上的周末和假期等不连续性或日内图表上的交易缺口。 scaleLinear
将您的数据视为原始数字,忽略这些数字背后的任何含义。这就是为什么你的轴刻度不能很好地舍入并且缩放功能感觉不佳的原因 - 因为 scaleLinear
并不是为处理基于时间的数据的细微差别而设计的。
核心问题在于
scaleLinear
和scaleTime
如何解释域。 scaleTime
知道时间是连续但不均匀的,而scaleLinear
假设时间是均匀的。当您使用 scaleLinear
时,它期望连续的数字范围而没有间隙,而基于时间的数据则不是这种情况。
要使
scaleLinear
模仿 scaleTime
的行为,您需要手动处理刻度值和格式。这意味着生成尊重数据间隙的刻度值并适当地格式化它们。您可以创建一个考虑周末、假期和交易缺口的自定义报价生成器。但我们要明确一点,这并不是一件小事 - 您本质上是在复制 scaleTime
的部分功能。
您当前的方法会生成 x 轴,但显示不正确,因为
scaleLinear
不像 scaleTime
那样对值进行四舍五入。对于每日图表,这些值与该月的第一天不对齐,对于日内图表,时间值没有很好地舍入。
如果您正在寻找与
scaleTime
相同的显示、舍入和缩放功能,但使用 scaleLinear
来处理不连续数据,则需要构建更复杂的系统。该系统应该模拟 scaleTime
的刻度生成和格式化行为。本质上,您是在线性刻度之上构建自定义时间刻度。
这就是 D3 的灵活性发挥作用的地方,它允许您定义自定义刻度值和格式。但请记住,这不仅仅是正确地勾选。您的缩放功能还需要考虑数据的不连续性,确保放大和缩小仍然感觉直观和准确。
简而言之,虽然
scaleLinear
为您提供了对不连续数据所需的控制,但您必须弥补固有时间意识的缺乏。这涉及自定义刻度生成和格式化逻辑,与 scaleTime
的自然行为方式保持一致,确保您的图表保持用户友好且信息丰富。这不是一项微不足道的任务,但只要仔细实施,您就可以达到预期的结果。
这里的代码表现出我刚才描述的方式:
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const allBars = [...]; // Your dataset
const xScale = d3.scaleLinear()
.domain([0, allBars.length - 1])
.range([0, width - margin.left - margin.right]);
const numTicks = 10;
function generateCustomTicks(data, numTicks) {
let ticks = [];
let step = Math.ceil(data.length / numTicks);
for (let i = 0; i < data.length; i += step) {
ticks.push(i);
}
return ticks;
}
function formatCustomTick(index, data) {
if (index >= 0 && index < data.length) {
const timestamp = new Date(data[index].t);
return d3.timeFormat("%Y-%m-%d %H:%M")(timestamp);
}
return '';
}
const xAxis = d3.axisBottom(xScale)
.tickValues(generateCustomTicks(allBars, numTicks))
.tickFormat((d, _) => formatCustomTick(d, allBars));
// Create SVG
const svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
// Append x-axis
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(${margin.left},${height - margin.bottom})`)
.call(xAxis);
const zoom = d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
function zoomed(event) {
const transform = event.transform;
const newScale = transform.rescaleX(xScale);
xAxis.scale(newScale)
.tickValues(generateCustomTicks(allBars, numTicks))
.tickFormat((d, _) => formatCustomTick(d, allBars));
svg.select('.x-axis').call(xAxis);
}
svg.call(zoom);