D3似乎太复杂了,基本上要用any个框架来工作……特别是在打字方面,因为他们显然一直在改变他们的API,使得这么多来自经济学家和其他帖子的过go 的例子几乎毫无用处.

目前,我想使用TypeScrip和SolidJS the canonical force graph example.

我感觉像是在try 解决拼图游戏,因为许多类型就是不匹配.我认为,我遇到最大麻烦的是drag()个论点.当然,我也try 过简单地忽略类型,但我也无法使其工作.

另外,有没有人知道是否可以将任意组件附加为图形 node ?


参考文献

  1. D3 - Force Directed Graph
  2. CM-Tech's D3 Graph for a SolidJS Debugger (in TypeScript)
  3. Easily show relationships — Draw Simple Force Graph with React & d3 utilizing TypeScript
  4. Building D3 interactive network graph D3 Force-Simulation + React + TypeScript
  5. mbostock's Gist
  6. steveharoz's Gist with Full Controls
  7. YouTube - React Tutorials & Tips For ReactJs, d3 Developers - Integrating d3.js with React - Force Chart - Force network graph - Intro and setting up
  8. YouTube - React Tutorials & Tips For ReactJs, d3 Developers - Integrating d3.js with React - Force Chart - Force network graph - Network graph graphics

推荐答案

要创建像使用SolidJS的docs example中那样的FDG(强制定向图),几乎没有SolidJS特定的东西,主要是关于了解d3是如何工作的,什么函数调用做了什么,通过那个函数知道在哪里做什么(是使用副作用,还是在挂载时做一些事情,这就是我所说的"在哪里").

关于Typescript 的问题

大多数方法使用类型脚本泛型,并从参数中推断类型,但是,有时只添加类型参数会更好.为了不出现类型问题,这里有一个小提示-

100

您在使用d3.drag时遇到了问题,因为在默认情况下,泛型采用unknown类型(除非为它们分配了默认类型),如果您在带有类型参数的调用中显式,则ts不会抱怨.

基于SolidJS的FDG文档示例

Here's如何创建如示例中所示的图表-

import * as d3 from "d3";
import { onMount } from "solid-js";

interface FDGNode extends d3.SimulationNodeDatum {
  id: string;
  group: number;
}

interface FDGLink extends d3.SimulationLinkDatum<FDGNode> {
  value: number;
}

export type FDGData = {
  nodes: Array<FDGNode>;
  links: Array<FDGLink>;
};

let svgRef: SVGSVGElement | undefined;

export default function ForceDirectedGraph(props: {
  children?: never;
  data: FDGData;
}) {
  const width = 928;
  const height = 600;
  const color = d3.scaleOrdinal(d3.schemeCategory10);

  const links = props.data.links.map((d) => ({ ...d }));
  const nodes = props.data.nodes.map((d) => ({ ...d }));

  onMount(() => {
    if (svgRef) {
      const simulation = d3
        .forceSimulation(nodes)
        .force(
          "link",
          d3.forceLink<FDGNode, FDGLink>(links).id((d) => d.id)
        )
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));

      const link = d3
        .select<SVGSVGElement, FDGNode>(svgRef)
        .selectAll<SVGLineElement, FDGLink>("line")
        .data(links);

      const node = d3
        .select<SVGSVGElement, FDGNode>(svgRef)
        .selectAll<SVGCircleElement, FDGNode>("circle")
        .data(nodes);

      function ticked() {
        link
// The source property comes from the SimulationLinkDatum interface, and by default,
// has a type of TypeOfNode | string | number, where TypeOfNode is FDGNode in this example
// so we need to make an as assertion. The x,y properties added by the SimulationNodeDatum
// interface are of type number | undefined, so a non-null assertion operator is used
          .attr("x1", (d) => (d.source as FDGNode).x!)  
          .attr("y1", (d) => (d.source as FDGNode).y!)
          .attr("x2", (d) => (d.target as FDGNode).x!)
          .attr("y2", (d) => (d.target as FDGNode).y!);

        node.attr("cx", (d) => d.x!).attr("cy", (d) => d.y!);
      }
      simulation.on("tick", ticked);

      function dragstarted(
        event: d3.D3DragEvent<SVGCircleElement, FDGNode, FDGNode>
      ) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
      }

      function dragged(
        event: d3.D3DragEvent<SVGCircleElement, FDGNode, FDGNode>
      ) {
        event.subject.fx = event.x;
        event.subject.fy = event.y;
      }

      function dragended(
        event: d3.D3DragEvent<SVGCircleElement, FDGNode, FDGNode>
      ) {
        if (!event.active) simulation.alphaTarget(0);
        event.subject.fx = null;
        event.subject.fy = null;
      }

      node.call(
        d3
          .drag<SVGCircleElement, FDGNode>()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended)
      );
    }
  });

  return (
    <svg
      ref={svgRef}
      width={width}
      height={height}
      viewBox={`0 0 ${width} ${height}`}
      style={{
        "max-width": "100%",
        height: "auto"
      }}
    >
      <g stroke="#999" stroke-opacity={0.6}>
        {links.map((link) => {
          return <line stroke-width={Math.sqrt(link.value)} />;
        })}
      </g>
      <g stroke={"#fff"} stroke-width={1.5}>
        {nodes.map((node) => (
          <circle r={5} fill={color(`${node.group}`)}>
            <title>{node.id}</title>
          </circle>
        ))}
      </g>
    </svg>
  );
}

图形 node

一旦模拟被渲染(在文档中的特定示例中,使用SVG容器),FDG内部的交互仅由d3处理,因此从SolidJS重新渲染方面不需要太多.因为它是一个SVG容器,所以它只能有svg elements个子容器.

然而,完全有可能使用任何任意组件作为图形 node ,为此,我们必须使用一个svg容器(除非您愿意使用path个元素来绘制路径,然后使用一个svg容器),那么可能是一个div容器?这样,所有交互的配置都必须通过添加所有不同的处理程序来完成,这有点像您在问题中链接的CM-Tech's D3 Graph for a SolidJS Debugger (in TypeScript)个示例.有关更多细节,请查看this题.

Typescript相关问答推荐

TypeScript实用程序类型—至少需要一个属性的接口,某些指定属性除外

有没有可能使用redux工具包的中间件同时监听状态的变化和操作

有没有一种方法可以在保持类型的可选状态的同时更改类型?

如何键入函数以只接受映射到其他一些特定类型的参数类型?

如何根据特定操作隐藏按钮

Express Yup显示所有字段的所有错误

PrimeNG日历需要找到覆盖默认Enter键行为的方法

如何将所有props传递给React中的组件?

使用2个泛型类型参数透明地处理函数中的联合类型

路由链接始终返回到

TypeScrip中的类型安全包装类

在类型脚本中将泛型字符串数组转换为字符串字母并集

RXJS将对键盘/鼠标点击组合做出react

避免混淆两种不同的ID类型

TS2739将接口类型变量赋值给具体变量

作为函数参数的联合类型的键

如何创建由一个帐户签名但使用另一个帐户支付的Hedera交易

导航链接不触发自定义挂钩

有没有一种简单的方法可以访问Keyboard Shortcuts JSON文件中列出的每个命令的显示名称(标题)?

从 npm 切换到 pnpm 后出现Typescript 错误