我有一个简单的图表,其中时间作为X轴。预期的行为是,在拖动图形时,X轴只会平移以显示数据的其他部分。
为方便起见,由于我的X轴位于react组件中,因此创建我的图表的函数将X比例尺,x轴及其附加的元素设置为this.xScale
,this.xAxis
,和this.gX
。
如果将其设置为缩放方法的内容,则一切正常:
this.gX.call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)))
通过触摸输入,X轴平滑移动。但是,这对我不起作用,因为稍后在更新图表时(响应轴的变化而移动数据点),我需要更改this.xAxis
以便点将映射到不同的位置。
因此,我然后将缩放方法的内容设置为此:
this.xScale = d3.event.transform.rescaleX(this.xScale);
this.xAxis = this.xAxis.scale(this.xScale);
this.gX.call(this.xAxis);
据我所知,这应该完全相同。但是,当我使用此代码时,即使不运行我的updateChart()
函数(更新数据点),平移时X轴也会按比例缩放,这比正常情况大得多。我的X轴是基于时间的,所以从2014年到2018年的时域突然包括1920年代初。
我在做什么错了?
答案 0 :(得分:2)
使用scale.rescaleX时,您将基于当前的缩放变换(基于平移和缩放)来修改缩放的域。
但是,从d3.event.transfrom
返回的变换不是上一个缩放变换的变化,它表示累积变换。我们要在原始比例上应用此变换,因为该变换表示相对于原始状态的更改。但是,您正在按先前的缩放变换修改的比例应用此累积变换:
this.xScale = d3.event.transform.rescaleX(this.xScale);
让我们通过平移等翻译事件来完成此工作:
那行得通,但是如果我们再次平移:
为什么?因为缩放变换跟踪相对于初始状态的缩放状态,但是您只想用状态变化而不是缩放变换的累积变化来更新比例。因此,此时域已移动30个单位,但用户只能平移20个单位。
规模同样会发生:
在第四步,d3.event.transform.k == 4
,并且rescaleX现在将比例缩放为四分之一,它并不“知道”缩放已经 被缩放了一个因子两个。
如果我们继续应用缩放,情况会变得更糟,例如,如果我们从k = 4缩小到k = 2,d3.event.transform.k == 2
,尽管尝试缩小,我们仍在放大2倍,现在在16x:2x4x2。相反,如果我们放大,则得到64x(2x4x8)
此效果在平移上特别糟糕-甚至在平移事件中也会连续触发缩放,因此,缩放比例将在已累计应用缩放转换的缩放比例上重新应用。平移可以轻松触发数十个缩放事件。在下面的比较代码段中,尽管起步时间为2014-2018,但平移一下就可以轻松将您带入1920年代。
最简单的方法(和规范方法)与您在代码中用于平移(但不能更新)的方法非常相似:
this.gX.call(this.xAxis.scale(d3.event.transform.rescaleX(this.xScale)))
我们在这里做什么?我们正在创建一个新的比例,同时保持原始比例不变-d3.event.transform.rescaleX(this.xScale)
。我们向轴提供新的比例尺。但是,正如您注意到的那样,在更新图形时会遇到问题,xScale
不是轴使用的比例,因为我们现在有两个不同的比例。
然后的解决方案是使用我所说的参考秤和工作秤。参考比例将用于基于当前缩放变换更新工作比例。每当创建/更新轴或点时,都将使用工作刻度。开始时,两个比例可能会相同,因此我们可以这样创建比例:
var xScale = d3.scaleLinear().domain(...).range(...) // working
var xScaleReference = xScale.copy(); // reference
我们可以照常使用xScale更新或放置元素。
在缩放时,我们可以使用以下命令更新xScale(和轴):
xScale = d3.event.transform.rescaleX(xScaleReference)
xAxis.scale(xScale);
selection.call(xAxis);
这是一个比较,它具有与您所注意到的相同的域,但是很快就可以到达较高等级的1920年代(使用一个等级)。底部比预期的要多得多(并使用工作和参考比例):
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 200);
var parseTime = d3.timeParse("%Y")
var start = parseTime("2014");
var end = parseTime("2018");
///////////////////
// Single scale updated by zoom transform:
var a = d3.scaleTime()
.domain([start,end])
.range([20,380])
var aAxis = d3.axisBottom(a).ticks(5);
var aAxisG = svg.append("g")
.attr("transform","translate(0,30)")
.call(aAxis);
/////////////////
// Reference and working scale:
var b = d3.scaleTime()
.domain([start,end])
.range([20,380])
var bReference = b.copy();
var bAxis = d3.axisBottom(b).ticks(5);
var bAxisG = svg.append("g")
.attr("transform","translate(0,80)")
.call(bAxis);
/////////////////
// Zoom:
var zoom = d3.zoom()
.on("zoom", function() {
a = d3.event.transform.rescaleX(a);
b = d3.event.transform.rescaleX(bReference);
aAxisG.call(aAxis.scale(a));
bAxisG.call(bAxis.scale(b));
})
svg.call(zoom);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
我们可以看到Mike Bostock的示例brush and zoom所采用的相同方法,其中x2和y2代表参考比例,x和y代表工作比例。