为我的用例修复D3-zoom API

2020-10-24 07:31:56

D3-ZOOM是令人难以置信的健壮、强大和灵活的。但是,它的灵活性意味着要正确配置和使用它可能相当复杂。这篇文章介绍了一种包装它的方法,降低了它的灵活性,但极大地简化了它在我的用例中的使用。

为了塑造讨论,我们将创建下面的图表,允许您使用鼠标或触摸交互平移和缩放绘图区域-。

第一节介绍了一个如何使用d3-zoom的示例。如果你已经吮吸过那个特定的鸡蛋,我建议跳到下一节。

Const prng=d3。随机正态();常量数据=d3。范围(1 E3)。Map(d=>;({x:prng(),y:prng()}));const x=d3.。Scale线性()。Domain([-5,5]);const y=d3。Scale线性()。域([-5,5]);常量x2=x。Copy();const y2=y。Copy();

这第一段代码只是在原点和关联的x/y标尺周围创建一些随机的x/y数据点,以呈现具有硬编码域的值。

这段代码中值得注意的部分是最后两行,它们创建刻度的副本。这些将在稍后的…中非常重要。

其余的代码片段使用D3FC组件。但是,基本概念应该熟悉香草D3,并且很容易应用到香草D3。

常量级数=fc。SeriesCanvasPoint()。CrossValue(d=>;d..。X)。MainValue(d=>;d..。Y)。大小(4);常量缩放=d3。ZOOM()。在(';缩放';,事件=>;{x.。域(事件。变形。RescaleX(X2)。Domain());y。域(事件。变形。重新缩放Y(Y2)。Domain());Render();});

同样,这段代码开始时非常乏味,只是创建了一个配置为呈现数据点的点系列。

此代码的第二部分是大多数操作发生的地方。我们创建一个d3-zoom组件/行为,并侦听当用户与选择交互时触发的缩放事件(我们将在下面介绍这如何与选择相关联)。

在事件处理程序中,我们收到一个zoomTransform,它表示自缩放行为应用于选择(或显式重置)以来所有交互的组合效果。

这意味着我们需要在先前创建的x/y比例副本(x2/y2)上使用变换中的重缩放工具,而不是使用先前重新缩放的副本(x/y)。实际上,复制的比例表示应应用变换的基线。先前重新缩放的是已应用变换的工作比例。

常量图表=fc。图表笛卡尔(x,y)。Chart Label(画布缩放1000点)。CanvasPlotArea(系列)。装饰(sel=>;{sel.。输入()。选择(';.Plot-Area';)。在(';测量范围';,事件=>;{x2.。范围([0,事件。细节。宽度]);y2。范围([事件。细节。高度,0]);})。Call(Zoom);});

此代码块创建一个笛卡尔图表,然后使用x/y刻度和系列对其进行配置。装饰函数实际上是唯一有趣的部分,当创建绘图区时-。

侦听测量事件以确保x/y刻度副本上的范围值保持最新。它们由rescaleX/Y变换工具在内部使用。

最后,我们有呈现函数本身,它将数据与目标容器相关联并调用图表组件。

这样,我们就获得了上面示例所需的所有代码。向图表添加缩放行为真的不是很好的代码。D3-zoom组件为我们处理了大量的低级交互处理。

然而,组件的灵活性确实意味着API不像上面的用例那样符合人体工程学。理想情况下,我们不需要关注细节,比如维护第二组比例、将变换应用到这些比例或保持这些比例上的范围。

将上面的痛点作为更好的API的灵感来源,此用例的理想组件将另外-。

既然这个假设组件提供了一种将比例与选择相关联的简明方式,那么最好解决另一个使用d3-zoom很难实现的常见要求-。

这在多个面板共享单个x比例、小倍数图表共享两个比例或更微妙的可用性考虑因素(例如允许用户平移或缩放轴以仅改变其关联比例)的图表中会很有用。

在这个阶段,我确信我们实现了这样一个新组件并不令人惊讶。下面的图表与前面的示例几乎完全相同,使用D3FC-ZOOM创建。

要允许在轴上平移和缩放,对上面的“假设”代码唯一需要的更改是调用轴选择上的组件。它改变了装饰功能,如下所示-。

图表。装饰(sel=>;{sel.。输入()。选择All(';.lot-Area';)。调用(zoom,x,y);sel.。输入()。选择全部(';.x轴';)。Call(zoom,x,null);sel。输入()。选择全部(';.y轴';)。Call(zoom,null,y);});

尽管使用了selectAll,但每个轴只有一个容器。它在功能上等同于SELECT,但避免了对用于创建轴容器的数据联接的干扰。

在内部,该组件使用d3-zoom组件,注册其交互处理程序并响应缩放事件。它不使用转换来跟踪用户交互的状态,而是直接将该状态存储在比例域中。

这具有允许多个实例在比例域值上同步的优点。它还允许通过其他代码(例如,单击按钮、流式传输数据等)直接更新比例域,而不必担心转换。

这种方法的明显缺点是,尝试处理不连续的比例会有困难。通过直接使用比例域值作为我们的状态,我们丢失了变换的基于中间像素的表示。

公平警告:这会影响到实现的杂草。如果您只是尝试使用该组件,则不需要了解这些细节,我建议跳到下一节。

为了简化本节中的解释,我使用SCALE作为x和/或y SCALE的简写。此外,虽然组件支持多节点选择,但我假设选择只包含一个节点。

首先,让我们考虑一下d3-zoom是如何工作的。假设存在与选择相关联的现有转换(T0)。用户的交互使组件产生到该转换的增量(DT)。

这两个值组合在一起以产生传递到事件处理程序的转换(t1=t0+dt)。此转换还根据选择存储为DOM节点上的属性。

如果我们已经将先前的变换应用于比例,那么现有变换和增量的这种组合就会导致问题。在这种情况下,我们有效地双重应用了前面的转换,导致了对用户交互的一些令人兴奋/令人困惑的指数响应。

就其核心而言,D3FC-ZOOM执行的解决方法与我们上面演示的相同。当与选择相关联时,还会向该组件传递工作刻度。这与应用转换时用作基线的副本一起存储在DOM节点上。

基线比例的范围将使用工作比例的当前范围进行更新。

使用重缩放工具将基线比例与变换组合在一起,以生成结果比例。

D3FC-ZOOM的新部分允许外部修改工作秤的范围。这可以通过显式设置域或通过与相同比例相关联的不同选择上的缩放事件来实现。

为了处理这种情况,该组件另外存储-*另一个刻度的副本,表示刻度的先前或最后观察到的状态(在执行重新定标操作之后)。*表示转换的上一次或最后一次观察到的状态的转换副本。

如果它检测到工作秤的域与其先前状态不同,则它知道该域已被外部修改。在这种情况下,简单地将传递到事件处理程序的转换应用于基线刻度会产生不正确的结果,因为t0不表示前一个刻度和基线刻度之间的差异。

相反,该组件计算变换增量(dt=t1-t0),将基线重置为工作比例的新副本,将增量(Dt)应用于此基线,然后使用结果域更新工作比例。

与编写代码相比,用语言来描述要困难得多,因此快速浏览一下实现可能会更好地理解。

我毫不怀疑,我们为D3FC-ZOOM创建的新API将不能灵活地处理其他人的用例,或者更糟糕的是,无法满足其他人的用例。但是,对于我熟悉实现的常见用例,我发现它非常强大且易于使用。

事实上,我会争辩说,通常情况下,通过给问题添加约束,你就会让自己获得更简单的解决方案。最终使您能够专注于您认为重要的细节的解决方案。

我叫克里斯·普莱斯,我住在泰恩河畔的纽卡斯尔,是斯科特逻辑公司的一名软件工程师。我每天都在为金融服务公司开发桌面/平板电脑/移动网络应用程序(不幸的是,这些应用程序大多隐藏在付费墙后面)。如果可以的话,我确实喜欢写博客,在GitHub上做一些有趣的事情。