我正在尝试使用 d3.js V4 创建甘特图组件。我使用的是旧版本,因此我也可以使用 d3-xyzoom,这有助于解决其他滚动问题。经过大量的试验和错误以及复制和粘贴之后,我想要的行为几乎已经实现。我不是一个 JS 编码员,而且对 d3.js 很陌生。
当我在计算机浏览器上运行下面的代码片段时,其行为正是我想要的:我可以平移块并仅在 X 轴上放大它们。当我在 iPad 上尝试相同的代码时,滚动行为发生了变化,我发现如果所有内容都向上平移,我就无法再放大特定块。当我再次缩小时,一切都会平移回来。
这是我到目前为止所得到的:
const WIDTH = 1180;
const HEIGHT = 820;
const START_DATE = new Date(2023, 0, 1);
const END_DATE = new Date(2023, 11, 31);
const BLOCK_HEIGHT = 20;
const BLOCKS = makeBlocks();
var xyzoom = d3.xyzoom();
xyzoom.scaleRatio([1, 0]);
const svg = d3.select("#chart").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.on("touchstart", function () {
d3.event.preventDefault();
});
const g = svg.append("g")
.attr("cursor", "grab");
var xScale = d3.scaleTime()
.domain([START_DATE, END_DATE])
.range([20, WIDTH - 20]);
var xAxis = d3.axisTop(xScale)
.tickFormat(d3.timeFormat('%b %Y'));
var axisG = svg.append("g")
.attr("transform", "translate(0,50)")
.call(xAxis);
g.selectAll("rect.block")
.data(BLOCKS)
.enter()
.append("rect")
.attr("class", "block")
.attr("x", d => xScale(d.start))
.attr("width", d => xScale(d.end) - xScale(d.start))
.attr("y", d => d.y)
.attr("height", BLOCK_HEIGHT)
.attr("fill", d => d.colour);
svg.call(xyzoom
.extent([[0, 0], [WIDTH, HEIGHT]])
.scaleExtent([.5, 8])
.on("zoom", zoomer));
function zoomer() {
const FULL_DATE_RANGE_MILLISECS = 604800000;
const WEEK_NUMBER_RANGE_MILLISECS = 21427200000;
let transform = d3.event.transform;
transform.ky = 1;
g.attr("transform", `translate(${transform.x},${transform.y}) scale(${transform.kx},${transform.ky})`);
var new_xScale = transform.rescaleX(xScale);
var visibleTimeSpan = new_xScale.domain()[1] - new_xScale.domain()[0];
if (visibleTimeSpan < FULL_DATE_RANGE_MILLISECS) {
xAxis.tickFormat(d3.timeFormat('%b %d, %y'));
} else if (visibleTimeSpan < WEEK_NUMBER_RANGE_MILLISECS) {
xAxis.tickFormat(function (date) {
return "#" + getWeekNumber(date) + ', \'' + d3.timeFormat('%y')(date);
});
} else {
xAxis.tickFormat(d3.timeFormat('%b %Y'));
}
axisG.call(xAxis.scale(new_xScale));
}
function makeBlocks() {
const NUMBER_OF_BLOCKS = 50;
let yPos = 40;
let blocks = Array.from({ length: NUMBER_OF_BLOCKS }).map((_, index) => {
let randomStartDate = new Date(START_DATE.getTime() + Math.random() * (END_DATE.getTime() - START_DATE.getTime()));
let randomEndDate = new Date(randomStartDate.getTime() + Math.random() * (END_DATE.getTime() - randomStartDate.getTime()));
yPos += 25;
let colour = index === 5 ? 'darkred' : 'steelblue';
return {
start: randomStartDate,
end: randomEndDate,
y: yPos,
colour
};
});
return blocks;
}
function getWeekNumber(d) {
const DAY_LENGTH_MILLISECS = 86400000;
var date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
var dayNum = date.getUTCDay() || 7;
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
var yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
return Math.ceil((((date - yearStart) / DAY_LENGTH_MILLISECS) + 1) / 7);
}
欢迎提供有关如何在 iPad 上解决此问题的任何建议。
我将您的代码移至片段并使用 d3v7。变焦在 ipad 上效果更好一点
const WIDTH = 1180;
const HEIGHT = 820;
const START_DATE = new Date(2023, 0, 1);
const END_DATE = new Date(2023, 11, 31);
const BLOCK_HEIGHT = 20;
const BLOCKS = makeBlocks();
const svg = d3.select("#chart").append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.on("touchstart", function() {
d3.event.preventDefault();
});
const g = svg.append("g")
.attr("cursor", "grab");
var xScale = d3.scaleTime()
.domain([START_DATE, END_DATE])
.range([20, WIDTH - 20]);
var xAxis = d3.axisTop(xScale)
.tickFormat(d3.timeFormat('%b %Y'));
var axisG = svg.append("g")
.attr("transform", "translate(0,50)")
.call(xAxis);
g.selectAll("rect.block")
.data(BLOCKS)
.enter()
.append("rect")
.attr("class", "block")
.attr("x", d => xScale(d.start))
.attr("width", d => xScale(d.end) - xScale(d.start))
.attr("y", d => d.y)
.attr("height", BLOCK_HEIGHT)
.attr("fill", d => d.colour);
svg.call(d3.zoom()
.extent([
[0, 0],
[WIDTH, HEIGHT]
])
.scaleExtent([.5, 8])
.on("zoom", zoomer));
function zoomer({
transform
}) {
let rescaleX = transform.rescaleX(xScale);
g.selectAll("rect.block")
.data(BLOCKS)
.attr("x", d => rescaleX(d.start))
.attr("width", d => rescaleX(d.end) - xScale(d.start))
axisG.call(d3.axisTop(rescaleX)
.tickFormat(d3.timeFormat('%b %Y')));
}
function makeBlocks() {
const NUMBER_OF_BLOCKS = 50;
let yPos = 40;
let blocks = Array.from({
length: NUMBER_OF_BLOCKS
}).map((_, index) => {
let randomStartDate = new Date(START_DATE.getTime() + Math.random() * (END_DATE.getTime() - START_DATE.getTime()));
let randomEndDate = new Date(randomStartDate.getTime() + Math.random() * (END_DATE.getTime() - randomStartDate.getTime()));
yPos += 25;
let colour = index === 5 ? 'darkred' : 'steelblue';
return {
start: randomStartDate,
end: randomEndDate,
y: yPos,
colour
};
});
return blocks;
}
function getWeekNumber(d) {
const DAY_LENGTH_MILLISECS = 86400000;
var date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
var dayNum = date.getUTCDay() || 7;
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
var yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
return Math.ceil((((date - yearStart) / DAY_LENGTH_MILLISECS) + 1) / 7);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
</style>
</head>
<body>
<div id="chart"></div>
<script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
</script>