npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@gravity-ui/graph

v0.0.1

Published

Modern graph editor component

Downloads

13

Readme

Graph editor library

library for graph visualization

Install and setup

npm install @gravity-ui/graph
import { GraphCanvas, GraphState, TRenderBlockFn } from "@gravity-ui/graph";
import classNames from "classnames";
import React, { useRef } from "react";
import { BlockMenuPortal } from "./Layers/BlockMenuLayer/BlockMenuPortal";
import { useGraphColors } from "./hooks/useGraphColors";
import { useRenderBlock } from "./hooks/useRenderBlock";
import { useGraphController } from "./Context";
export type TGraphComponentProps = {
  className?: string;
};

export function GraphEditor(props: TGraphComponentProps) {

  const { graph } = useGraphController();

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

  return (
    <GraphCanvas
      graph={graph}
      renderBlock={renderBlock}
      onStateChanged={({ state }) => {
        if (state === GraphState.ATTACHED) {
          graph.start();
        }
      }}
    />
  );
}

Graph configuration

You can configure editor with object like:

export const exampleConfig: TGraphConfig = {
  configurationName: "simple",
  blocks: [
    {
      x: 265,
      y: 334,
      width: 200,
      height: 160,
      id: "Lonely block without anchors" as TBlockId,
      is: "Block",
      selected: false,
      name: "one block",
      anchors: [],
      meta: "whatever your want as any" as any,
    },
  ],
  connections: [],
  cameraScale: 0.9,
  // start camera poision
  rect: {
    x: -156,
    y: 0,
    width: 631,
    height: 494,
  },
  settings: {
    // make your graph readonly with this settings:
    canChangeBlockGeometry: ECanChangeBlockGeometry.NONE,
    canDragCamera: false,
    canCreateNewConnections: false,
    canDuplicateBlocks: false,
  },
};

Graph events

To respond to graph events, we have implemented a event system based on the DOM EventTarget API and CustomEvent

const graph new Graph(...props);

const unsubscribe = graph.on("mouseenter", (event) => {
  console.log("mouseenter", event.detail);
  console.log('hovered element', event.detail.target);
  console.log('original event', event.detail.sourceEvent);

  event.preventDefault();
  event.stopPropagation();
}, {
  once: true,
  caprute: true;
});

unsubscribe();

Events

Mouse events

  • mousedown
  • click
  • mouseenter
  • mousemove
  • mouseleave

This event has a type of Graphmodelevent, with possible targets of Block, Connection, Anchor or Camera. If the event fires when the cursor is not over any element, the target will be the Camera.

type GraphMouseEvent<E extends Event = Event> = CustomEvent<{
  target?: EventedComponent;
  sourceEvent: E;
  pointerPressed?: boolean;
}>

graph.on('click', (event: GraphMouseEvent) => {
  console.log('clicked element', event.detail.target);
  console.log('original event', event.detail.sourceEvent);
  console.log('prevent click on element', event.preventDefault());
})

Preventing these events will prevent delegate this event to the target component so preventing selection, Drag and drop, other behavior implemented on the components.

Block Events

block-change Fires on change block position

graph.on('block-change', (event: CustomEvent<{ block: TBlock }>) => {
  console.log('Changed block', event.detail.block);
})



block-anchor-selection-change Fires when user select block's anchor\

IMPORTANT: Multiple anchor selection is not currently supported, so this event will only fire for one anchor.

graph.on('block-anchor-selection-change', (event: CustomEvent<{ anchor: TAnchor }>) => {
  console.log('selected anchor', event.detail.anchor);
})



blocks-selection-change Fires on selecte/unselect blocks

graph.on('blocks-selection-change', (event: CustomEvent<SelectionEvent<TBlockId>>) => {
  console.log('List of selected block IDs', event.detail.list);
  console.log('List of recently selected block IDs', event.detail.changed.add);
  console.log('List of unselected block IDs', event.detail.changed.removed);

  // Prevent apply selection changes
  event.preventDefault();
})



block-drag-start: Fires on before start drag event. Preventing stop the drag event

graph.on('block-drag-start', (event: CustomEvent<{ nativeEvent: MouseEvent; block: TBlock }>) => {
  console.log('drag block', event.detail.block);
  // prevent drag block
  event.predentDefault();
})



block-drag

graph.on('block-drag', (event: CustomEvent<{ nativeEvent: MouseEvent; block: TBlock }>) => {
  console.log('drag block', event.detail.block);
  console.log('next position', event.detail.x, event.detail.y);
  // prevent apply next block position
  event.predentDefault();
})



block-drag-end

graph.on('block-drag-end', (event: CustomEvent<{ nativeEvent: MouseEvent; block: TBlock }>) => {
  console.log('dropped block', event.detail.block);
  // prevent do nothing. This event only reset the drag state
  event.predentDefault();
})

Connection events

connection-selection-change Fires on selecte/unselect connection

graph.on('connection-selection-change', (event: CustomEvent<SelectionEvent<TConnection>>) => {
  console.log('List of selected coneections', event.detail.list);
  console.log('List of recently selected coneections', event.detail.changed.add);
  console.log('List of unselected coneections', event.detail.changed.removed);

  // Prevent apply selection changes
  event.preventDefault();
})

Camera events

camera-change: Fires on camera change state - move, zoom, reset viewport, ...etc

graph.on('camera-change', (event: CustomEvent<TCameraState>) => {
  console.log('camera change', event.detail);
  // prevent apply camera change
  event.preventDefault();
})

Event in React

To listen for events on the GraphComponent in React, you can use the onEventName prop. This is only available for some of the most common events, however. If you need to listen for other events, you can use a graphRef and the on method.

List of most usefull events

export type TGraphEventCallbacks = {
  click: (data: UnwrapGraphEventsDetail<"click">, event: UnwrapGraphEvents<"click">) => void;
  onCameraChange: (data: UnwrapGraphEventsDetail<"camera-change">, event: UnwrapGraphEvents<"camera-change">) => void;
  onBlockDragStart: (
    data: UnwrapGraphEventsDetail<"block-drag-start">,
    event: UnwrapGraphEvents<"block-drag-start">
  ) => void;
  onBlockDrag: (data: UnwrapGraphEventsDetail<"block-drag">, event: UnwrapGraphEvents<"block-drag">) => void;
  onBlockDragEnd: (data: UnwrapGraphEventsDetail<"block-drag-end">, event: UnwrapGraphEvents<"block-drag-end">) => void;
  onBlockSelectionChange: (
    data: UnwrapGraphEventsDetail<"blocks-selection-change">,
    event: UnwrapGraphEvents<"blocks-selection-change">
  ) => void;
  onBlockAnchorSelectionChange: (
    data: UnwrapGraphEventsDetail<"block-anchor-selection-change">,
    event: UnwrapGraphEvents<"block-anchor-selection-change">
  ) => void;
  onBlockChange: (data: UnwrapGraphEventsDetail<"block-change">, event: UnwrapGraphEvents<"block-change">) => void;
  onConnectionSelectionChange: (
    data: UnwrapGraphEventsDetail<"connection-selection-change">,
    event: UnwrapGraphEvents<"connection-selection-change">
  ) => void;
};

For Example

const YourPrettyGraphComponent = () => {
  const onBlockSelectionChange = useCallback((detail: SelectionEvent<TBlockId>, event: CustomEvent<SelectionEvent<TBlockId>>) => {
    console.log('List of selected block IDs', detail.list);
    console.log('List of recently selected block IDs', detail.changed.add);
    console.log('List of unselected block IDs', detail.changed.removed);
    event.preventDefault();
  }, []);
 return <GraphComponent 
   onBlockSelectionChange={onBlockSelectionChange}
 />
}

Available settings

Setup your graph behaviour and appearance with this set of settings:

export type TGraphSettingsConfig = {
  canDragCamera: boolean;
  canZoomCamera: boolean;
  canDuplicateBlocks: boolean;
  canChangeBlockGeometry: ECanChangeBlockGeometry;
  canCreateNewConnections: boolean;
  scaleFontSize: number;
  showConnectionArrows: boolean;
  useBezierConnections: boolean;
  useBlocksAnchors: boolean;
  connectivityComponentOnClickRaise: boolean;
  showConnectionLabels: boolean;
};

Graph editing hotkeys and mechanics

  • Create new connection by pressing shift + left click on source block. Drag mouse in target block and unpress mouse.

Events

connection-create-start Fires on start pulling new connection Preventing this event will prevent the connection from being created

graph.on("connection-create-start", (event) => {
  console.log('create new connection from block', event.detail.blockId);
  if(event.detail.anchorId) {
    console.log('from anchor', event.detail.anchorId);
  }
})

connection-create-hover Fires on new connection hover on block or anchor

graph.on("connection-create-hover", (event) => {
  console.log('create connection from', event.detail.sourceBlockId, 'to', event.detail.targetBlockId);
  event.detail.sourceAnchorId && console.log('from source block\'s anchor', event.detail.sourceAnchorId)
  event.detail.targetAnchorId && console.log('to target block\'s anchor', event.detail.sourceAnchorId);

  // Prevent create connection to target block/anchor
  event.preventDefault();
})

connection-created: Fires on drop new connection to block/anchor

graph.on("connection-created", (event) => {
  console.log('mouseup fires');
  console.log('create connection from', event.detail.sourceBlockId, 'to', event.detail.targetBlockId);
  event.detail.sourceAnchorId && console.log('from source block\'s anchor', event.detail.sourceAnchorId)
  event.detail.targetAnchorId && console.log('to target block\'s anchor', event.detail.sourceAnchorId);

  // Prevent create connection to target block/anchor
  event.preventDefault();
})
  • Select multiple blocks by pressing cmd + left click.

  • Duplicate existing block by pressing alt + left click. A so-called "shadow block" will appear. Library won't create new block when button released. You should use graph api in order to create new block.

Events

block-add-start-from-shadow: Fires on block start moving shadow block

graph.on("block-add-start-from-shadow", (event) => {
 console.log('start creating and moving shadow block', event.detail.sourceBlockId);

  // Prevent creating shadow block
  event.preventDefault();
})

block-added-from-shadow: Fires on shadow block dropped

graph.on("block-added-from-shadow", (event) => {
 console.log('drop shadow block', event.detail.sourceBlockId, 'to position', event.detail.coord);

  // Prevent creating shadow block
  event.preventDefault();
})

Update graph state with Public api.

List of methods in your disposition:

  public zoomToRect(rect: TGeometry, transition?: number): void;

  public zoomToBlocks(blockIds: TBlockId[], transition?: number): void;

  public zoomToViewPort(transition?: number): void;

  public getCurrentConfiguration(): TGraphConfig;

  public setBlockName(blockId: TBlockId, newName: string): void;

  public setSetting(flagPath: keyof TGraphSettingsConfig, value: boolean | number | ECanChangeBlockGeometry): void;

  public setCurrentConfigurationName(newName: string): void;

  public deleteSelected(): void;

  public selectBlocks(blockIds: TBlockId[], selected: boolean): void;

  public getBlockById(blockId: TBlockId): TBlock;

  public getUsableRect(): TGeometry;

  public addBlock(geometry: TGeometry, name: string: void): TBlockId;

  public addConnection(connection: TConnection): string

  public updateConnection(id: string, connection: TConnection): void;

  public selectConnections(connectionIds: string[], selected: boolean): void;

API-Example. Block update

useEffect(() => {
  setTimeout(() => {
    if (!graphRef) return;
    graphRef.current.api.updateBlock({
      id: "block id",
    });
  }, 1000);
}, []);