Skip to main content

Vertex drawing

This plugin provides a means for users to draw new nodes and groups onto the canvas, or into existing groups. When drawing a group users can snag existing vertices on the canvas for inclusion in the new group.

Instantiation​

Manual mode​

To use this plugin "manually" (discussed below), you declare it in the plugins section of the render options for some Surface:

import { Component, ViewChild } from '@angular/core';
import { SurfaceComponent } from '@visuallyjs/browser-ui-angular';
import { VertexDrawingPlugin } from "@visuallyjs/browser-ui"


@Component({
selector: 'app-root',
template: ` <vjs-surface [renderOptions]="renderOptions"></vjs-surface> `
})
export class AppComponent {
renderOptions = {
useModelForSizes: true,
plugins: [
VertexDrawingPlugin.type
]
};
}

Note the useModelForSizes:true declaration: this is recommended, if you intend to let your users drag new vertices via a lasso. There is also a mode in which your users can click on the canvas to add new vertices, and in that mode you may not need to use the model for sizes. This is discussed below

Integrated with ShapePalette​

To use this plugin with a ShapePalette, you need to instruct the palette to run in "tap" mode:


import { SURFACE_MODE_TAP } from "@visuallyjs/browser-ui"

@Component({
selector:"app-component",
template:`<div>
<vjs-surface [viewOptions]="viewOptions"/>
<vjs-shape-palette [dataGenerator]="dataGenerator" [mode]="SURFACE_MODE_TAP"/>
</div>`
</div>
})

export class AppComponent {

viewOptions = {... }

dataGenerator(el:Element) {
return {
...
}
}
}

With this setup, the ShapeLibraryPalette will register a suitable configured instance of the vertex drawing plugin on the surface. It also handles setting the surface into vertexDrawing mode in order to activate the plugin at the appropriate time.

For a full discussion of dragging/drawing new SVG shapes, please see this page


Manual usage​

With the declaration shown above you get a default setup of the plugin, which will draw groups, of type default. In order to switch on the plugin, you have to set the appropriate mode in the surface:

import { SURFACE_MODE_VERTEX_DRAWING  } from "@visuallyjs/browser-ui"

mySurface.setMode(SURFACE_MODE_VERTEX_DRAWING)

In this first example we've declared a vertex drawing plugin and then made the call shown above to activate the plugin. Try clicking and dragging on the canvas below - the plugin will add new groups. You can also draw groups inside other groups.

We setup the surface using the code snippet shown above - with the default options on the vertex drawing plugin, but also with useModelForSizes switched on:

import { Component, ViewChild } from '@angular/core';
import { SurfaceComponent } from '@visuallyjs/browser-ui-angular';
import { VertexDrawingPlugin } from "@visuallyjs/browser-ui"


@Component({
selector: 'app-root',
template: ` <vjs-surface [renderOptions]="renderOptions"></vjs-surface> `
})
export class AppComponent {
renderOptions = {
useModelForSizes: true,
plugins: [
VertexDrawingPlugin.type
]
};
}
note

For comparison, here's the same setup but without useModelForSizes:true. Notice how the groups you add always end up the same size, regardless of the size you draw them:

Specifying vertex details​

The default setup of this plugin will always create new groups, and those groups will have type of default. For a more fine-grained control of the plugin, you have a few options.

Specifying group type​

You can instruct the plugin to use a specific group type via the groupType parameter:

import { Component, ViewChild } from '@angular/core';
import { SurfaceComponent } from '@visuallyjs/browser-ui-angular';
import { VertexDrawingPlugin } from "@visuallyjs/browser-ui"


@Component({
selector: 'app-root',
template: ` <vjs-surface [renderOptions]="renderOptions"></vjs-surface> `
})
export class AppComponent {
renderOptions = {
useModelForSizes: true,
plugins: [
{
type: VertexDrawingPlugin.type,
options: {
groupType: "userDrawn"
}
}
]
};
}

In this example we've added a couple of groups manually, whose type is "default", and so, per the view above, they are drawn with an orange-red box. But if you drag new groups in this canvas, their type will be userDrawn, resulting in a cadetblue box (one of my favourite default HTML colors).

Drawing nodes​

To draw nodes instead of groups, supply a nodeType parameter to the plugin:

import { Component, ViewChild } from '@angular/core';
import { SurfaceComponent } from '@visuallyjs/browser-ui-angular';
import { VertexDrawingPlugin } from "@visuallyjs/browser-ui"


@Component({
selector: 'app-root',
template: ` <vjs-surface [renderOptions]="renderOptions"></vjs-surface> `
})
export class AppComponent {
renderOptions = {
useModelForSizes: true,
plugins: [
{
type: VertexDrawingPlugin.type,
options: {
nodeType: "userDrawn"
}
}
]
};
}

For the sake of clarity here we've declared a default and also a userDrawn type for groups and for nodes. On the canvas we've added a group and a node, both of type default. But if you now draw a new vertex on here, the nodeType parameter set on the plugin will instruct the plugin to draw nodes, of type userDrawn, in this case. So you'll see a box that is coloured darkmagenta when you draw:

Choosing type at runtime​

A common use case for this plugin is to draw vertices of some type that a user has selected in the UI (that's the case for the ShapePalette, for instance, which is discussed below). To support this, instead of supply a hard-coded groupType or nodeType, you can supply a typeGenerator, which allows you to not only choose the type of vertex to create, it allows you to choose between a node or a group, and also to provide an initial payload. The signature for the typeGenerator function is:

export type VertexDrawingPluginTypeGenerator = (origin:PointXY, e:MouseEvent) => {objectType:typeof Node.objectType | typeof Group.objectType, type:string, data?:ObjectData}

So it is given the origin of the event (in canvas coordinates) and the originating mouse event, and is expected to return an object of this type:

{
type:string,
objectType:"Group"|"Node",
data?:ObjectData
}

Here we have the same example as just above, but in this case we've added radio button to allow you to specify what type of object to draw. Remember, groups are cadetblue and nodes are darkmagenta.

Our plugin spec looks like this:

plugins:[
{
type:tk.VertexDrawingPlugin.type,
options:{
typeGenerator:(origin, evt) => {
return {
objectType:document.querySelector("[type='radio']:checked").value,
type:"userDrawn"
}
}
}
}
]

...we return the value of the currently checked radio button. How you go about determining this value will depend on your app. Notice also that here we've hard-coded the type, but that is also, of course, something you can derive dynamically.

Clicking to add vertices​

All of the preceding examples have been focused on the user dragging an area on the screen to draw new vertices, but it is also possible to allow "click to add":

plugins:[
{
type:tk.VertexDrawingPlugin.type,
options:{
typeGenerator:(origin, evt) => {
return {
objectType:document.querySelector("[type='radio']:checked").value,
type:"userDrawn"
},
allowClickToAdd:true
}
}
}
]

When you have this enabled, the plugin will use a default size for nodes of 80 pixels width and height, and for groups of 250 pixels width and height. You can set those values:

plugins:[
{
type:tk.VertexDrawingPlugin.type,
options:{
typeGenerator:(origin, evt) => {
return {
objectType:document.querySelector("[type='radio']:checked").value,
type:"userDrawn",
defaultNodeSize:{width:100, height:80},
defaultGroupSize:{width:500, height:350}
},
allowClickToAdd:true
}
}
}
]

Advanced usage​

Providing a vertex preview element​

By default, the vertex drawing plugin uses a [lasso](plugins-overview#lasso) to draw an area on the canvas that the new vertex will occupy. You can, though, provide a function that will generate an element at the start of a drag to be drawn as a child of the lasso:

vertexPreviewGenerator?:(p:PointXY, e:MouseEvent) => BrowserElement

This is the code for the example above:

plugins:[
{
type:tk.VertexDrawingPlugin.type,
options:{
vertexPreviewGenerator:(p, e) => {

const div = document.createElement("div")
div.style.position="absolute"
div.style.width="100%"
div.style.height="100%"
div.style.display="flex"
div.style.alignItems="center"

const d = document.createElement("marquee")
d.style.width="100%"
d.setAttribute("scrollamount", 15)
d.innerHTML = "I am drawing a new element"
div.appendChild(d)

return div
}
}
}
]

It's almost certain if you use this functionality you'll do something more sensible with it. The ShapeLibraryPalette, for example, draws the current selected shape into an element and returns that.

Updating the preview element​

Depending on what the HTML for your preview element looks like, you may need to update it as the user drags. In our example above we did not; we used some HTML elements and had CSS take care of the positioning for us. But in some cases - particularly if working with SVG - you might want to update the element. For that you can provide a vertexPreviewUpdater:

vertexPreviewUpdater?:(p:PointXY, size:Size, el:BrowserElement) => any

Try dragging a new group here:

For this we provided a vertexPreviewGenerator and also a vertexPreviewUpdater:

plugins:[
{
type:VertexDrawingPlugin.type,
options:{
vertexPreviewGenerator:(p, e) => {

const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect")
rect.setAttribute("fill", "#567567")
rect.setAttribute("x", "0")
rect.setAttribute("y", "0")
rect.setAttribute("width", "50%")
rect.setAttribute("height", "50%")
svg.appendChild(rect)

const rect2 = document.createElementNS("http://www.w3.org/2000/svg", "rect")
rect2.setAttribute("fill", "#984267")
rect2.setAttribute("x", "50%")
rect2.setAttribute("y", "50%")
rect2.setAttribute("width", "50%")
rect2.setAttribute("height", "50%")
svg.appendChild(rect2)

return svg
},
vertexPreviewUpdater:(p, size, el) => {
el.setAttribute("width", size.width)
el.setAttribute("height", size.height)
}
}
}
]

The preview element is an SVG element, and the updater sets its width and height. It could probably be argued, in fact, that this case could also have been handled via CSS, but there are internal use cases in VisuallyJs that cannot, and so there may well be use cases in your app that cannot either.


Usage with ShapePalette​

The ShapePalette can integrate with a vertex drawing plugin. The way to switch this on is to set the mode flag on the palette to SURFACE_MODE_TAP (or "tap", if you just want to use a string):


import { SURFACE_MODE_TAP, ShapeLibraryPalette, ShapeLibraryImpl } from "@visuallyjs/browser-ui"

const shapeLibrary = new ShapeLibraryImpl(...)

new ShapePalette({
container:document.querySelector(".node-palette"),
surface,
mode:SURFACE_MODE_TAP,
shapeLibrary,
dataGenerator:(el) => {
return {
name:el.getAttribute("data-type"),
details:""
}
}
})

The palette will now create a vertex drawing plugin and attach it to the surface. The palette takes advantage of several of the capabilities discussed above:

  • A typeGenerator is provided to the plugin which returns the objectType and type of the currently selected item in the palette. Additionally, any dataGenerator set on the palette will be invoked and used to prepare an initial dataset for a new vertex.
  • A vertexPreviewGenerator is provided, which draws a copy of the currently selected element (which is SVG).
  • A vertexPreviewUpdater function is provided, which redraws the SVG according to the new dimensions.

Try clicking an element in the palette here and dragging new nodes on the canvas:


Grids​

When the associated surface has a grid, this plugin will constrain node/group sizes so that the dimensions of new vertices are a multiple of the grid in each axis.


CSS​

ClassTable id="vertexDrawing"/>


Options​

VertexDrawingPluginOptions
Options for the vertex drawing plugin
NameTypeDescription
allowClickToAdd?booleanDefaults to false. When true, users can click on the canvas to "draw" a new element, and the plugin will use a default size for the element. When this flag is set, drag to add is still also enabled.
autoExit?booleanWhen true (which is the default) the lasso exits after a group has been drawn
clickToAddOnly?booleanDefaults to false. When true, the plugin only supports click to add new vertices, not drag. This flag is forced to true if the associated UI does not have useModelForSizes set, since there is no point in allowing a user to drag a vertex to some size if its not going to be honoured. When you set this flag and associated UI does have useModelForSizes set, the plugin will use default sizes for new nodes/groups.
defaultGroupSize?SizeOptional default size for groups, used when allowClickToAdd is set to true. Defaults to width and height of 250 pixels.
defaultNodeSize?SizeOptional default size for nodes, used when allowClickToAdd is set to true. Defaults to width and height of 80 pixels.
groupType?stringThe type identifier for new groups. If you have also provided a typeGenerator this will be ignored. If you have not provided a typeGenerator but you have provided a nodeType, this will be ignored.
invert?booleanDefaults to false, meaning the lasso is drawn as a rectangle. If true, the lasso is drawn as a set of masks, with the lasso area drawn as a "hole" in the masks.
lassoClass?stringOptional extra class(es) to set on the lasso used for drawing.
minSize?SizeThe minimum size for vertices. If your user draws a vertex smaller than this it will be discarded. Defaults to a width/height of 50 pixels.
nodeType?stringThe type identifier for new nodes. If you have also provided a typeGenerator this will be ignored.
onVertexAdded?(v:Vertex) => anyCallback to invoke when a vertex has been added
typeGenerator?VertexDrawingPluginTypeGeneratorOptional function that will be invoked when a new drag commences, and which is responsible for returning the type of object to add (Node or Group), as well as that object's own type, and, optionally, an initial payload for the new object.
vertexPreviewGenerator?(p:PointXY, e:MouseEvent) => BrowserElementA function that will be invoked at the start of a drawing operation, and can return an element to draw into the lasso. This is an optional function; if you don't provide it VisuallyJs will draw a basic SVG rectangle to indicate the drawing area.
vertexPreviewUpdater?(p:PointXY, size:Size, el:BrowserElement) => anyA function that is invoked during mouse drag, to be used in conjunction with vertexPreviewGenerator. This function gives you a hook to use to make any changes to your preview element, should you need to. This is optional.