【问题标题】:Svelte and D3 brushSvelte 和 D3 笔刷
【发布时间】:2020-01-17 06:36:11
【问题描述】:

我很难理解如何将 Svelte 与 D3 的 brush project 之类的东西一起使用。 Svelte 使用声明性方法进行操作。在area chart example 中,线条的SVG 被写在模板HTML 中。要使用 D3 执行此操作,您将使用 Javascript 函数调用来选择一个元素并调用另一个函数来修改 DOM。在上述图表示例中,D3 缩放库仅用于生成轴数组,但 HTML 本身由 Svelte 管理。 Svelte 以这种方式工作是有道理的——用函数调用构建东西会不那么干净,但我不知道如何用画笔做到这一点。如何在我的 Svelte 模板中以声明方式构建画笔 HTML,这将如何影响画笔事件之类的事情?最好只使用 onMount 内部的画笔函数,并将更改事件与本地 Svelte 变量联系起来?

【问题讨论】:

    标签: d3.js brush svelte


    【解决方案1】:

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

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

    【讨论】:

    • 感谢您的确认。我喜欢你说的“想要负责 DOM”的方式。这是概念化它的好方法。
    • 我正忙于手动处理 D3 画笔功能。 Svelte 足够干净,我认为这将是一个很好的解决方案,但它让我担心其他库如何合作(它们都需要 Svelte 扩展才能玩得很好吗?)
    • 我很想看看你是怎么做的,如果你不介意在准备好后分享你的代码?
    • 当然没问题,我把代码贴在这里。应该是一天左右。
    • 嘿@Mikkel 我设法让它工作(见我自己的答案)。它很乱,但它有效!
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      D3 只是与 Svelte 重叠,因为它也在屏幕上呈现内容。但是如果你仔细想想,如果你已经有了 Svelte,你就真的不需要渲染器了。一旦有了渲染器,关于图表的复杂部分实际上就是定位。这就是 D3 真正 的亮点所在。如果您从两个世界中“挑选”最好的,您实际上最终会获得出色的开发体验。 但是,唉,您也可以将渲染留给 D3。但是,您需要尽可能避免 Svelte。

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

      1. Svelte 仅渲染容器 DOM,然后交给 D3 进行计算和渲染。您仅在 onMount 和 onDestroy 期间在两个世界之间进行交互
      2. 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(coordinate) 将它们转换为值
      • 使用这些值,例如更新您的图表

      像这样:

       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});
          }
        }
      

      希望这可以帮助其他为此苦苦挣扎的人。祝你好运!

      【讨论】:

        【解决方案4】:

        您的问题没有单一答案,但我认为最好的选择是使用 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(工兵),因此任何对 d3 的select 的调用都只能在浏览器中完成。上面代码中的 if 情况处理了这一点,因为 bind:this 指令只会在运行客户端时设置 brushElement

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多