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 排除在外。
基本上你有两个选项都很好:
- Svelte 仅渲染容器 DOM,然后交给 D3 进行计算和渲染。您仅在 onMount 和 onDestroy
期间在两个世界之间进行交互
- 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
。
我正在努力理解如何将 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 排除在外。
基本上你有两个选项都很好:
- Svelte 仅渲染容器 DOM,然后交给 D3 进行计算和渲染。您仅在 onMount 和 onDestroy 期间在两个世界之间进行交互
- 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
。