要创建像使用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题.