Skip to content

Commit

Permalink
feat: custom connections
Browse files Browse the repository at this point in the history
  • Loading branch information
Antamansid committed Nov 10, 2024
1 parent ef22813 commit 4e401c9
Show file tree
Hide file tree
Showing 19 changed files with 2,739 additions and 65 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ npm install @gravity-ui/graph
- [Customization connection](https://preview.gravity-ui.com/graph/?path=/story/api-updateconnection--default)

```jsx
import { GraphCanvas, GraphState, TRenderBlockFn, useGraph } from "@gravity-ui/graph";
import { GraphCanvas, GraphState, TRenderBlockFn, GraphBlock, useGraph } from "@gravity-ui/graph";
import React from "react";

const config = {};
Expand Down Expand Up @@ -62,8 +62,8 @@ export function GraphEditor() {
})
})

const renderBlock = (graph, block) => {
return <HTMLBlockView graph={graph} block={block} />;
const renderBlockFn = (graph, block) => {
return <GraphBlock graph={graph} block={block}>{block.id}</GraphBlock>;
};

return (
Expand All @@ -86,4 +86,3 @@ export function GraphEditor() {
- [Pulic API](docs/public_api.md)
- [Graph Events](docs/events.md)
- [Editing](docs/editing.md)

73 changes: 40 additions & 33 deletions src/components/canvas/connections/BlockConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Block } from "../blocks/Block";
import { GraphLayer, TGraphLayerContext } from "../layers/graphLayer/GraphLayer";

import withBatchedConnection from "./batchMixins/withBatchedConnection";
import { TConnectionRenderSettings } from "./batchMixins/withBatchedConnections";
import { bezierCurveLine, generateBezierParams, getArrowCoords, isPointInStroke } from "./bezierHelpers";
import { getLabelCoords } from "./labelHelper";

Expand Down Expand Up @@ -51,19 +52,19 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo

protected readonly unsubscribe: (() => void)[];

private sourceBlock: Block;
public sourceBlock: Block;

private sourceAnchor?: TAnchor;
public sourceAnchor?: TAnchor;

private targetBlock: Block;
public targetBlock: Block;

private targetAnchor?: TAnchor;
public targetAnchor?: TAnchor;

private hitBoxHash = 0;
public hitBoxHash = 0;

protected path2d?: Path2D;
public path2d?: Path2D;

private geometry = { x1: 0, x2: 0, y1: 0, y2: 0 };
public geometry = { x1: 0, x2: 0, y1: 0, y2: 0 };

private labelGeometry = { x: 0, y: 0, width: 0, height: 0 };

Expand Down Expand Up @@ -104,10 +105,13 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
this.sourceBlock = this.connectedState.$sourceBlock.value?.getViewComponent();
this.targetBlock = this.connectedState.$targetBlock.value?.getViewComponent();

if (this.connectedState.sourceAnchorId && this.connectedState.targetAnchorId) {
if (this.connectedState.sourceAnchorId) {
this.sourceAnchor = this.sourceBlock.connectedState
.getAnchorById(this.connectedState.sourceAnchorId)
?.asTAnchor();
}

if (this.connectedState.targetAnchorId) {
this.targetAnchor = this.targetBlock.connectedState
.getAnchorById(this.connectedState.targetAnchorId)
?.asTAnchor();
Expand Down Expand Up @@ -136,18 +140,17 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
return;
}

this.shouldRender = this.context.camera.isLineVisible(
this.geometry.x1,
this.geometry.y1,
this.geometry.x2,
this.geometry.y2
);
this.shouldRender = this.isConnectionVisible();
}

public computeRenderSettings(props, state: TConnectionState) {
public isConnectionVisible() {
return this.context.camera.isLineVisible(this.geometry.x1, this.geometry.y1, this.geometry.x2, this.geometry.y2);
}

public computeRenderSettings(props, state: TConnectionState): TConnectionRenderSettings {
let lineWidth = 2;
let zIndex = 1;
let lineDash: number[] | boolean = false;
let lineDash: number[] | undefined;
const strokeStyle = this.getStrokeColor(state);

if (state.selected) {
Expand Down Expand Up @@ -180,14 +183,18 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
protected didRender() {
super.didRender();

const hash = this.geometry.x1 + this.geometry.y1 + this.geometry.x2 + this.geometry.y2;
const hash = this.calculateHitBoxHash();

if (this.hitBoxHash !== hash) {
this.hitBoxHash = hash;
this.updateHitBox();
}
}

public calculateHitBoxHash() {
return this.geometry.x1 + this.geometry.y1 + this.geometry.x2 + this.geometry.y2;
}

public handleEvent(event: MouseEvent | KeyboardEvent) {
event.stopPropagation();

Expand All @@ -208,7 +215,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
}
}

private updateHitBox = () => {
public updateHitBox = () => {
const labelGeometry = this.labelGeometry;
let minX: number;
let maxX: number;
Expand Down Expand Up @@ -254,7 +261,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo

public onHitBox(shape: HitBoxData): boolean {
const THRESHOLD_LINE_HIT = this.context.constants.connection.THRESHOLD_LINE_HIT;
const relativeTreshold = THRESHOLD_LINE_HIT / this.context.camera.getCameraScale();
const relativeThreshold = THRESHOLD_LINE_HIT / this.context.camera.getCameraScale();
const x = (shape.minX + shape.maxX) / 2;
const y = (shape.minY + shape.maxY) / 2;

Expand All @@ -264,25 +271,25 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
this.props.useBezier && this.path2d
? isPointInStroke(this.context.ctx, this.path2d, shape.x, shape.y, THRESHOLD_LINE_HIT * 2)
: intersects.boxLine(
x - relativeTreshold / 2,
y - relativeTreshold / 2,
relativeTreshold,
relativeTreshold,
this.geometry.x1,
this.geometry.y1,
this.geometry.x2,
this.geometry.y2
);
x - relativeThreshold / 2,
y - relativeThreshold / 2,
relativeThreshold,
relativeThreshold,
this.geometry.x1,
this.geometry.y1,
this.geometry.x2,
this.geometry.y2
);

if (this.labelGeometry !== undefined) {
return (
superHit &&
(intersectsLine ||
intersects.boxBox(
x - relativeTreshold / 2,
y - relativeTreshold / 2,
relativeTreshold,
relativeTreshold,
x - relativeThreshold / 2,
y - relativeThreshold / 2,
relativeThreshold,
relativeThreshold,
this.labelGeometry.x,
this.labelGeometry.y,
this.labelGeometry.width,
Expand All @@ -294,7 +301,7 @@ export class BlockConnection extends withBatchedConnection(withHitTest(EventedCo
}
}

private updateGeometry(sourceBlock: Block, targetBlock: Block) {
public updateGeometry(sourceBlock: Block, targetBlock: Block) {
if (!sourceBlock || !targetBlock) return;
const scale = this.context.camera.getCameraScale();
const isSchematicView = scale < this.context.constants.connection.SCALES[1];
Expand Down
12 changes: 10 additions & 2 deletions src/components/canvas/connections/BlockConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export class BlockConnections extends withBatchedConnections(Component) {

public removeFromRenderOrder = this.removeFromRenderOrder.bind(this);

protected connectionsView = {};

constructor(props: {}, parent: GraphLayer) {
super(props, parent);
this.unsubscribe = this.subscribe();
Expand All @@ -28,6 +30,7 @@ export class BlockConnections extends withBatchedConnections(Component) {

protected subscribe() {
this.connections = this.context.graph.rootStore.connectionsList.$connections.value;
this.connectionsView = this.context.graph.rootStore.settings.getConfigFlag("connectionComponents");

const r1 = this.context.graph.rootStore.settings.$connectionsSettings.subscribe(() => {
this.scheduleUpdate();
Expand All @@ -38,7 +41,12 @@ export class BlockConnections extends withBatchedConnections(Component) {
this.scheduleUpdate();
});

return [r1, r2];
const r3 = this.context.graph.rootStore.settings.$connectionComponents.subscribe((connectionsView) => {
this.connectionsView = connectionsView;
this.scheduleUpdate();
});

return [r1, r2, r3];
}

protected unmount() {
Expand All @@ -60,7 +68,7 @@ export class BlockConnections extends withBatchedConnections(Component) {
showConnectionLabels: settings.showConnectionLabels,
showConnectionArrows: settings.showConnectionArrows,
};
return BlockConnection.create(props, { key: String(connection.id) });
return (this.connectionsView[connection.$state.value.is] || BlockConnection).create(props, { key: String(connection.id) });
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { TGraphLayerContext } from "../../layers/graphLayer/GraphLayer";
import { IWithBatchedConnection } from "./withBatchedConnection";

export interface IWithBatchedConnections {
addInRenderOrder(comp: unknown, settings: object): void;
addInRenderOrder(comp: unknown, settings: TConnectionRenderSettings): void;
removeFromRenderOrder(comp: unknown): void;
}

type ConnectionComponent = Component & IWithBatchedConnection;
type StyleSettings = object & { zIndex: number };
export type TConnectionRenderSettings = Partial<
Pick<CanvasRenderingContext2D, "fillStyle" | "lineWidth" | "strokeStyle">
> & { zIndex: number; lineDash?: number[] };

// default export because https://github.com/microsoft/TypeScript/issues/30355#issuecomment-671095933
export default <T extends Constructor<Component>>(superclass: T): T & Constructor<IWithBatchedConnections> =>
Expand All @@ -18,15 +20,15 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo

private mapKeyToComps = new Map<string, Set<ConnectionComponent>>();

private mapKeyToSettings = new Map<string, StyleSettings>();
private mapKeyToSettings = new Map<string, TConnectionRenderSettings>();

private mapCompToComps = new WeakMap<ConnectionComponent, Set<ConnectionComponent>>();

private wasModifyed = false;
private wasModified = false;

private sortedCouplesKeySetting: [string, StyleSettings][] = [];
private sortedCouplesKeySetting: [string, TConnectionRenderSettings][] = [];

public addInRenderOrder(comp: ConnectionComponent, settings: StyleSettings) {
public addInRenderOrder(comp: ConnectionComponent, settings: TConnectionRenderSettings) {
const key = JSON.stringify(settings);
let comps = this.mapCompToComps.get(comp);

Expand All @@ -46,7 +48,7 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo
}

this.mapCompToComps.set(comp, comps);
this.wasModifyed = true;
this.wasModified = true;
this.performRender();
}

Expand All @@ -61,40 +63,35 @@ export default <T extends Constructor<Component>>(superclass: T): T & Constructo
protected willIterate() {
super.willIterate();

if (this.wasModifyed) {
this.wasModifyed = false;
if (this.wasModified) {
this.wasModified = false;
this.sortedCouplesKeySetting = Array.from(this.mapKeyToSettings.entries()).sort((item1, item2) => {
return item2[1].zIndex - item1[1].zIndex;
});
}
}

protected render() {
let i;
let j;
let key;
let settings;
let comps;

for (i = 0; i < this.sortedCouplesKeySetting.length; i += 1) {
key = this.sortedCouplesKeySetting[i][0];
settings = this.sortedCouplesKeySetting[i][1];
for (let i = 0; i < this.sortedCouplesKeySetting.length; i += 1) {
const key = this.sortedCouplesKeySetting[i][0];
const settings = this.sortedCouplesKeySetting[i][1];

// because original will be changed during addInRenderOrder while ._iterate() below
comps = [...this.mapKeyToComps.get(key)];
const comps = [...this.mapKeyToComps.get(key)];

this.context.ctx.beginPath();

for (j = 0; j < comps.length; j += 1) {
for (let j = 0; j < comps.length; j += 1) {
comps[j]._iterate();
}

if ("lineDash" in settings && settings.lineDash) {
this.context.ctx.setLineDash(settings.lineDash);
}

Object.assign(this.context.ctx, settings);
this.context.ctx.stroke();

if (settings.strokeStyle) this.context.ctx.stroke();
if (settings.fillStyle) this.context.ctx.fill();

if ("lineDash" in settings) {
this.context.ctx.setLineDash([]);
Expand Down
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
export { Anchor, type TAnchor } from "./components/canvas/anchors";
export { Block as CanvasBlock, type TBlock } from "./components/canvas/blocks/Block";
export { Block as CanvasBlock, type TBlock, type TBlockProps } from "./components/canvas/blocks/Block";
export { generateBezierParams, isPointInStroke } from "./components/canvas/connections/bezierHelpers";
export {
BlockConnection as CanvasConnection,
type TConnectionProps,
} from "./components/canvas/connections/BlockConnection";
export { type GraphLayer } from "./components/canvas/layers/graphLayer/GraphLayer";
export * from "./graph";
export type { TGraphColors, TGraphConstants } from "./graphConfig";
export * from "./plugins";
export * from "./react-component";
export { ECameraScaleLevel } from "./services/camera/CameraService";
export { HitBoxData } from "./services/HitTest";
export * from "./services/Layer";
export * from "./store";
export { EAnchorType } from "./store/anchor/Anchor";
export type { BlockState, TBlockId } from "./store/block/Block";
export type { ConnectionState, TConnection, TConnectionId } from "./store/connection/ConnectionState";
export { ECanChangeBlockGeometry } from "./store/settings";
export { addEventListeners } from "./utils/functions";
export { dragListener } from "./utils/functions/dragListener";
export * from "./utils/renderers/text";
export { EVENTS } from "./utils/types/events";
export { type TPoint, type TRect } from "./utils/types/shapes";
Expand Down
2 changes: 1 addition & 1 deletion src/react-component/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const GraphBlock = <T extends TBlock>({
}: {
graph: Graph;
block: T;
children: React.ReactNode;
children?: React.ReactNode;
className?: string;
containerClassName?: string;
}) => {
Expand Down
5 changes: 3 additions & 2 deletions src/react-component/hooks/useGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { batch } from "@preact/signals-core";

import { ZoomConfig } from "../../api/PublicGraphApi";
import type { TBlock } from "../../components/canvas/blocks/Block";
import { Graph, GraphState, TGraphConfig } from "../../graph";
import type { TGraphZoomTarget } from "../../graph";
import { Graph, GraphState, TGraphConfig } from "../../graph";
import type { TGraphColors, TGraphConstants } from "../../graphConfig";
import type { Layer } from "../../services/Layer";
import type { TConnection } from "../../store/connection/ConnectionState";
import { TGraphSettingsConfig } from "../../store/settings";
import { useFn } from "../../utils/hooks/useFn";
import { RecursivePartial } from "../../utils/types/helpers";

Expand Down Expand Up @@ -57,7 +58,7 @@ export function useGraph(config: HookGraphParams) {
return {
graph,
api: graph.api,
setSettings: useFn((settings) => graph.updateSettings(settings)),
setSettings: useFn((settings: Partial<TGraphSettingsConfig>) => graph.updateSettings(settings)),
start: useFn(() => {
if (graph.state !== GraphState.READY) {
graph.start();
Expand Down
1 change: 1 addition & 0 deletions src/store/connection/ConnectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type TConnectionId = string | number | symbol;

export type TConnection = {
id?: TConnectionId;
is?: string;
sourceBlockId: TBlockId;
targetBlockId: TBlockId;
sourceAnchorId?: string;
Expand Down
1 change: 1 addition & 0 deletions src/store/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe("Settings store", () => {
connectivityComponentOnClickRaise: true,
showConnectionLabels: false,
blockComponents: {},
connectionComponents: {},
});
});
it("Should get config via key", () => {
Expand Down
Loading

0 comments on commit 4e401c9

Please sign in to comment.