- 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
- 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
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 EntityModel
s 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
};
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.
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
We'll cover this in the next section:
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
}
};
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.