Skip to content

opassion/react-uml

Repository files navigation

React UML

demo

Features

  • The data is the source of truth
  • Can add custom Components as entities
  • Continous feedback while editing
  • Configurable grid, snap to grid (or no grid at all)
  • History, undo, redo; keyboard shortcuts
  • Panel for adding new Entities, with drag or click
  • Automatic arrow placement
  • Labels for arrows

Missing features (currently working on)

  • Editable arrow labels (From the UI, you can always edit the model)
  • Panning, zooming (viewport and camera as separate concepts)
  • Editable arrow paths (From the UI, you can always edit the model)
  • Select several entities
  • Copy and paste entities
  • Alignment tools

[model-example.js]

The star of the show, the model that holds the data that will be represented as a diagram.

You don't need to conciously know the structure of the model, since you can always start a diagram via the UI, save it and resume from the UI without ever touching the model. But you may need this information for your specific use case.

The structure held in the model const can be defined in flow types as (you don't really need to know flowtype to grasp this):

type EntityState = Array<EntityModel>;

model is an array with EntityModels representing each entity

type EntityType = string;
type EntityModel = {
  id: EntityId,      // unique identifier of the Entity
  type: EntityType,  // type of entity, according to your custom entity components
  width: number,     // width
  height: number,    // height
  x: number,         // x position
  y: number,         // y position
  name: string,      // label of the entity
  linksTo?: Links,   // reference to other entities
  custom?: Object,   // custom data for you to extend functionalities
};

The id of an EntityModel is an EntityId; which is just a string

type EntityId = string;

The linksTo attribute is optional (an entity may not link to anyone) and holds a Links type, which is an array of Link

type Links = Array<Link>;

type Link = {
  target: EntityId,      // Id of another entity which is being linked to
  edited: boolean,       // whether or not the link was autogenerated or was edited by the user
  points?: Array<Point>, // Array of points that define the position of the link
  label?: string,        // link label
  color?: string,        // link color (currently not implemented)
};

The points attribute is also optional (as well as any attribute ending with ?) and holds an array of Point

type Point = {
  x: number,  // x position
  y: number,  // y position
};

[config-example.js]

In this file we have two configuration objects, config which deals with diagram configurations and customEntities which holds references to our custom components that represent each type of entity.

config can be defined as:

type ConfigState = {
  entityTypes: ConfigEntityTypes, // Size of entities
  gridSize?: number,              // optional grid size
};

ConfigEntityTypes is an object being used as a Map whose keys reference the types of entities.

type ConfigEntityTypes = {
  [EntityType]: {
    width: number,
    height: number,
  },
};

It's recommended to find an entitiy size that is a multiple of the grid size; however you're free to choose any positive number.

customEntities can be defined as:

type CustomEntities = {
  [EntityType]: {
    component: ComponentType<DiagComponentProps>,
    icon: {
      path: Element<*>,
      size: number,
    },
  },
};

The component attribute holds a reference to a React Component that will be provided DiagComponentProps props. The creation of your custom components is covered in {{TODO: Link to create custom entitiy components section}}

type DiagComponentProps = {
  model: EntityModel,
  meta: MetaEntityModel,
  setName: SetNamePayload => EntityAction,
};

You can use this props to get information about the component, and setName method to change the name of the component.

We referenced EntityModel before, and MetaEntityModel can be defined as:

type MetaEntityModel = {
  id: EntityId,
  isAnchored: boolean,
  isSelected: boolean,
};

Which is information that only matters while interacting with the components on the UI. You wouldn't need to save this information. A HOC [Entity component] will deal about positioning, selection, context menus and more for you, so you just need to focus on the specific features of your custom Entity.

[index.js]

On index we define our custom Component that initializes seting throguh componentWillMount and passes customEntities to the Diagram Component

import React from 'react';
import {
  Diagram,
  store as diagramStore,
  setEntities,
  setConfig,
  diagramOn,
} from 'react-uml';
import model from './model-example';
import { config, customEntities } from './config-example';

class CustomDiagram extends React.PureComponent {
  componentWillMount() {
    diagramStore.dispatch(setConfig(config));
    diagramStore.dispatch(setEntities(model));

    diagramOn('anyChange', entityState =>
      // You can get the model back
      // after modifying the UI representation
      console.info(entityState)
    );
  }
  render() {
    return <Diagram customEntities={customEntities} />;
  }
}

export default CustomDiagram;

We dispatch a setConfig and setEntities action to the diagramStore, and on anyChange we can get a hold of our modified entityState

event and task folders

We'll cover this in the next section:

[component.js]

Our custom component will be visually rendered on the UI in such a way that it uniquely identifies a specific concept. We can take for example BPMN's elements or UX flow diagrams. Perhaps in the future we'll include Packs with custom entities for these types of use cases, but for now you'll have to create your own.

Our entity can also deal with user interaction such as changing the name and whatever interaction you may come up with, with the possibility of using already present information in the EntityModel or adding your custom data to custom object field.

There are no specific requirements for the component. What we do need to know is that the component will be provided DiagComponentProps props, which encompasses:

type DiagComponentProps = {
  model: EntityModel,
  meta: MetaEntityModel,
  setName: SetNamePayload => EntityAction,
};

A usage example in [component.js]:

handleKeyPress = (ev) => {
  switch (ev.key) {
    case 'Enter':
      this.toggleEdit(false);
      this.props.setName({ id: this.props.model.id, name: this.state.name });
      break;
    case 'Escape':
      this.toggleEdit(false);
      this.setState({ name: this.props.model.name });
      break;
    // no default
  }
};

[icon.js]

We also need to provide an icon which will be used in the Panel for adding new elements and in the contextual menu for each entity to quickly add new entities.

import React from 'react';

const icon = {
  path: (
    <path d="M14 0H2C1 0 0 1 0 2v12c0 1 1 2 2 2h12c1 0 2-1 2-2V2c0-1-1-2-2-2z" />
  ),
  size: 16,
};

export default icon;

The icon consists of a path SVG React Element and a size that is the same as the size of viewBox SVG attribute.

The size attribute is provided so you don't need to transform the SVG element to fix exactly the needs of the panel or context menu. If we didn't have the size attribute, the SVG element may overflow or underflow its container.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published