Svelte 和 D3 画笔

Svelte and D3 brush

我正在努力理解如何将 Svelte 与 D3 之类的东西一起使用 brush project. Svelte operates using a declarative approach. In the area chart example 线条的 SVG 已写在模板 HTML 中。要使用 D3 执行此操作,您将使用 Javascript 函数调用 select 一个元素并调用另一个函数来修改 DOM。在上述图表示例中,D3 缩放库仅用于生成轴数组,但 HTML 本身由 Svelte 管理。 Svelte 以这种方式工作是有道理的——用函数调用构建东西会不那么干净,但我不知道如何用画笔来做到这一点。我如何以声明方式在我的 Svelte 模板中构建画笔 HTML,这将如何影响画笔事件等内容?最好只使用 onMount 内部的画笔函数和将事件更改为本地 Svelte 变量的排序吗?

同样的问题存在于React中,因为React和D3都想负责DOM。在 React 中,您只需调用指示 D3 在 ComponentDidMount 方法中工作的函数(如果使用钩子,则调​​用 useEffect。

Svelte 期望负责这种情况,您声明 UI 的构造方式,并定义操作,让它完成工作。它无法跟踪 D3 所做的事情,所以我怀疑您需要让 D3 负责该部分,而不用担心它会有点 hacky。

我自己成功做到了 https://svelte.dev/repl/00f726facd434b978c737af2698e0dbc?version=3.12.1

正如 Mikkel 在上面所说的那样,Svelte 的设计方式与 D3 之类的东西不能很好地自然搭配。在我看来,您有两个选择:尝试将 D3 事件连接到 Svelte 的反应变量中,或者尝试自己实现功能。

我选择了第二个版本。我采用了 D3 Brush 创建的 HTML 和 CSS,将鼠标处理程序添加到插入符号,并将所有变量反应性地绑定在一起。 (最后一部分我做的很乱。非常感谢其他 Svelte 用户对这个清洁器的任何反馈)。

我也花了一点时间来解决这个问题。但最终它实际上并没有那么复杂。他们的关键步骤是决定哪个图书馆负责哪个职责。

D3 只是与 Svelte 重叠,因为它也在屏幕上呈现内容。但是如果你仔细想想,如果你已经有了 Svelte,你就真的不需要渲染器了。一旦有了渲染器,图表的复杂部分实际上就是定位。这就是 D3 really 的亮点所在。如果您 "cherry pick" 两全其美,您实际上最终会获得出色的开发体验。 但是,唉,您也可以将渲染留给 D3。但是你需要尽可能地将 Svelte 排除在外。

基本上你有两个选项都很好:

  1. Svelte 仅渲染容器 DOM,然后交给 D3 进行计算和渲染。您仅在 onMount 和 onDestroy
  2. 期间在两个世界之间进行交互
  3. Svelte 呈现整体DOM,D3 提供图表位置。

至于画笔功能:

我发现创建一个带插槽的 ChartContainer(本质上只是一个 SVG)然后在其中放置一个 Brush 组件的效果最好。

<script>
  import { createEventDispatcher } from "svelte";

  export let minX;
  export let maxX;

  export let dX = 0;

  export let height;

  const dispatch = createEventDispatcher();

  let startX,
      endX,
      mouseDown = false,
      brushArea;

  function onMouseDown(event) {
    if (mouseDown) return;

    mouseDown = true;

    brushArea.removeEventListener("mousemove", onMouseMove);
    brushArea.removeEventListener("mouseup", onMouseUp);

    brushArea.addEventListener("mousemove", onMouseMove);
    brushArea.addEventListener("mouseup", onMouseUp);

    brushArea.style.cursor = "ew-resize";

    startX = Math.max(event.offsetX - dX, minX);
    endX = null;
  }

  function onMouseMove(event) {
    endX = Math.min(event.offsetX - dX, maxX);
  }

  function onMouseUp(event) {
    mouseDown = false;

    if (!endX) startX = null;

    brushArea.style.cursor = null;

    brushArea.removeEventListener("mousemove", onMouseMove);
    brushArea.removeEventListener("mouseup", onMouseUp);

    const active = !!startX;

    dispatch("brush", {active, startX, endX, clear});
  }

  function clear() {
    startX = null;
    endX = null;
  }

</script>

<rect class="ui--chart__brush_area"
      bind:this={brushArea}
      x={minX}
      y="0"
      height={height}
      width={maxX-minX}
      on:mousedown={onMouseDown}
/>

{#if endX != null}
  <rect class="ui--chart__brush"
        x={startX < endX ? startX : endX}
        y="0"
        height={height}
        width={startX < endX ? endX-startX : startX-endX}
  />
{/if}

dX 属性用于考虑左边距。您的用例可能需要也可能不需要(取决于您设置图表的方式)。关键是能够使用鼠标事件的 offsetX,这样您就可以知道鼠标事件触发的 SVG 边界距离多远。

那么,你就

  • 监听刷机事件
  • 提取坐标
  • 将它们转换为值 - 通过使用 yourScale.invert(坐标)
  • 使用这些值,例如更新您的图表

像这样:

 function onBrush(event) {
    const {active, startX, endX, clear} = event.detail;
    if (active) {
      const startDate = scaleX.invert(startX);
      const endDate = scaleX.invert(endX);

      dispatch("brush", {active, startX, endX, startDate, endDate, clear});
    }
  }

希望这可以帮助其他遇到此问题的人。祝你好运!

你的问题没有单一的答案,但我认为最好的选择是使用 svelte 的 html 呈现“最相关的数据”,将交互元素(如画笔)留给 运行 仅在客户端。

你应该知道 svelte 在内部将你的 html 转换为 js 生成器,因此调用 d3 函数实际上与 svelte 在客户端所做的非常相似。使用 svelte 的 html 而不是 d3 函数调用的唯一真正优势是 SSR,这就是将画笔仅保留在客户端是合理的原因(因为无论如何它都需要 js 进行交互)。

Svelte 是一种“反应式香草”,因此您几乎可以直接使用低级库。有时你需要做一些技巧来直接访问 DOM 元素(就像 d3 通常那样),为此我建议使用 bind:this 指令。示例:

<script>
  import { brushX, select } from 'd3';

  //...

  let brushElement;

  $: brush = brushX()
      .extent([[padding.left, padding.top], [width - padding.right, height - padding.bottom]])
      .on('end', onZoom)
  
  $: if (brushElement) {
    select(brushElement)
      .call(brush)
  }
</script>

<svg>
  ...
  <g bind:this={brushElement} width={...} height={...} />
  ...
</svg>

使用 DOM 的 API 时要考虑的一件事是 SSR(sapper),因此对 d3 的 select 的任何调用都应该只在浏览器中完成。上面代码中的 if 情况处理了这一点,因为 bind:this 指令只会在 运行 客户端时设置 brushElement