projectstorm / react-diagrams

a super simple, no-nonsense diagramming library written in react that just works
https://projectstorm.cloud/react-diagrams
MIT License
8.6k stars 1.17k forks source link

Custom Nodes Connections #746

Closed alexeybahterwork closed 3 years ago

alexeybahterwork commented 3 years ago

Authors, thank you for a wonderful library! How can I connect custom nodes? I created a custom node based on the example (https://github.com/projectstorm/react-diagrams/tree/master/packages/diagrams-demo-gallery/demos/demo-custom-node1). But in this example, the custom node is connected to the default node. Maybe someone has examples of connecting a custom node with a custom node? I'm just beginning to understand this library. Thanks!

dr-azbest commented 3 years ago

I face the same problem. When you using CustomNodeModel basen on demo example it dosent provide link method on PortModel as it have place for DefaultNodeModel:

image

I tried extending CustomNodeModel with DefaultNodeModel instead of NodeModel or couple of different things but I always end up with some errors.

How can we achieve linking custom nodes/portes ?

dr-azbest commented 3 years ago

Ok, I managed to achieve this by copiying DefaultNode to my project and adjust to personal needs

dylanvorster commented 3 years ago

@dr-azbest that would be the right way to do it since the idea is that the library can be extended with custom logic and components. @alexeybahterwork take a look at the source code of DefaultNode to see how this is done, the example demos also show this with the custom diamond node

LuisEgan commented 2 years ago

Here's how I managed to do it

DiamondNodeModel.ts based on NodeModel.ts from the source code:


import {
  DefaultNodeModelOptions,
  NodeModel,
  NodeModelGenerics,
  PortModelAlignment,
} from "@projectstorm/react-diagrams";
import { DiamondPortModel } from "./DiamondPortModel";

interface IDiamondNodeModel {
  title?: string;
}

export interface DiamondNodeModelGenerics extends NodeModelGenerics {
  PORT: DiamondPortModel;
  OPTIONS: DefaultNodeModelOptions;
}

export class DiamondNodeModel extends NodeModel<DiamondNodeModelGenerics> {
  protected ports: { [s: string]: DiamondPortModel };
  title: string;

  constructor(params?: IDiamondNodeModel) {
    super({
      type: "diamond",
    });

    this.title = params?.title || "";
    this.ports = {};

    this.addPort(new DiamondPortModel(PortModelAlignment.TOP));
    this.addPort(new DiamondPortModel(PortModelAlignment.LEFT));
    this.addPort(new DiamondPortModel(PortModelAlignment.BOTTOM));
    this.addPort(new DiamondPortModel(PortModelAlignment.RIGHT));
  }

  getPort(name: string): DiamondPortModel | null {
    return this.ports[name];
  }

  addPort(port: DiamondPortModel): DiamondPortModel {
    port.setParent(this);
    this.ports[port.getName()] = port;
    return port;
  }
}

DiamondPortModel.ts

import {
  LinkModel,
  PortModel,
  DefaultLinkModel,
  PortModelAlignment,
} from "@projectstorm/react-diagrams";
import { AbstractModelFactory } from "@projectstorm/react-canvas-core";

export class DiamondPortModel extends PortModel {
  constructor(alignment: PortModelAlignment) {
    super({
      type: "diamond",
      name: alignment,
      alignment,
    });
  }

  link<T extends LinkModel>(
    port: PortModel,
    factory?: AbstractModelFactory<T>,
  ): T {
    let link = this.createLinkModel(factory);
    link.setSourcePort(this);
    link.setTargetPort(port);
    return link as T;
  }

  createLinkModel(factory?: AbstractModelFactory<LinkModel>): LinkModel {
    let link = super.createLinkModel();
    if (!link && factory) {
      return factory.generateModel({});
    }
    return link || new DefaultLinkModel();
  }
}

And a little tweak to the DiamondNodeFactory.tsx

import { DiamondNodeWidget } from "./DiamondNodeWidget";
import { DiamondNodeModel } from "./DiamondNodeModel";
import * as React from "react";
import { AbstractReactFactory } from "@projectstorm/react-canvas-core";
import { DiagramEngine } from "@projectstorm/react-diagrams-core";

interface IDiamondNodeFactory {
  size?: number;
}

export class DiamondNodeFactory extends AbstractReactFactory<
  DiamondNodeModel,
  DiagramEngine
> {
  size: number = 50;

  constructor(params?: IDiamondNodeFactory) {
    super("diamond");

    this.size = params?.size ?? this.size;
  }

  generateReactWidget(event): JSX.Element {
    return (
      <DiamondNodeWidget
        engine={this.engine}
        size={this.size}
        node={event.model}
      />
    );
  }

  generateModel() {
    return new DiamondNodeModel();
  }
}

In my React component:

import { CanvasWidget } from "@projectstorm/react-canvas-core";
import createEngine, {
  DefaultNodeModel,
  DiagramEngine,
  DiagramModel,
  PortModelAlignment,
} from "@projectstorm/react-diagrams";
import * as React from "react";
import { useEffect, useState } from "react";
import { DiamondNodeFactory } from "./DiamondNodeFactory";
import { DiamondNodeModel } from "./DiamondNodeModel";
import { DiamondPortModel } from "./DiamondPortModel";
import { SimplePortFactory } from "./SimplePortFactory";

const Diagram= () => {
  const [engine, setEngine] = useState<DiagramEngine>();

  useEffect(() => {
    const engine = createEngine();

    // * Register custom factories
    engine
      .getPortFactories()
      .registerFactory(
        new SimplePortFactory(
          "diamond",
          () => new DiamondPortModel(PortModelAlignment.LEFT),
        ),
      );
    engine.getNodeFactories().registerFactory(new DiamondNodeFactory());

    //2) setup the diagram model
    const model = new DiagramModel();

    const X = 650;

    // * Nodes
    const customNode = new DiamondNodeModel({ title: "AYYE" });
    mainNode.setPosition(X, 100);

    const customNode2 = new DiamondNodeModel({ title: "AYYE22" });
    mainNode2.setPosition(X - 150, 500);

    // * Ports
    const leftMainNodePort = mainNode.getPort(PortModelAlignment.LEFT);
    const rightMainNodePort = mainNode.getPort(PortModelAlignment.RIGHT);

    // * Links
    const leftLinkCustomNode2 = leftMainNodePort.link(
      mainNode2.getPort(PortModelAlignment.LEFT),
    );
    const leftLinkCustomNode2 = rightMainNodePort.link(
      mainNode2.getPort(PortModelAlignment.LEFT),
    );

    // * Add models to the root graph
    model.addAll(
      customNode ,
      customNode2 ,
      leftMainNodePort ,
      rightMainNodePort ,
      leftLinkCustomNode2 ,
      leftLinkCustomNode2 
    );

    // * Lock dragging the nodes
    model.setLocked(true);

    //5) load model into engine
    engine.setModel(model);

    setEngine(engine);
  }, []);

  if (!engine) return null;

  //6) render the diagram!
  return <CanvasWidget className="w-full h-full bg-gray-600" engine={engine} />;
};

export default Diagram;