Skip to main content

Copy/paste

VisuallyJs offers an easy to use API for copy/paste - the Surface and Paper components expose a clipboard member, which offers a number of different methods. All paste operations are executed within a transaction and can be rolled-back/re-run as an atomic unit.

Copying objects​

Call the copy method to add items to the clipboard. The signature of the copy method is:

copy​

Copy some set of objects into the clipboard.
Signature
copy(obj:Base | VisuallyJsSelection | Array<Base> | Path)
Parameters
objBase | VisuallyJsSelection | Array<Base> | PathThe object, or objects, to copy in to the clipboard.
Return value
void

You can pass a variety of different types in to this method - any node, group or edge, or an array of these, or a Selection or Path.

In this first example, our canvas initially has two nodes. The addItemsToSelection function connects these two nodes with a programmatic call to addEdge, and it then copies both nodes and the new edge into the clipboard. The pasteItems method pastes the contents of the clipboard, to canvas origin {50, 250} (paste origin is discussed below).

import { useRef } from "react"
import { SurfaceComponent, SurfaceComponentRef, SurfaceProvider } from "@visuallyjs/browser-ui-react"

export default function MyComponent() {

const surfaceRef = useRef<SurfaceComponentRef>(null)

function addItemsToSelection() {
const surface = surfaceRef.current.getSurface()

const source = surface.model.getNode("source")
const target = surface.model.getNode("target")
const edge = surface.model.addEdge({source, target})

surface.clipboard.copy([source, target, edge]) // copy both nodes and the edge
}

function pasteItems() {
const surface = surfaceRef.current.getSurface()
surface.clipboard.paste({origin:{x:50, y:250}})
}

return <SurfaceProvider>
<SurfaceComponent ref={surfaceRef} data={data}/>
<button onClick={() => addItemsToSelection()}>Copy items</button>
<button onClick={() => pasteItems()}>Paste items</button>
</SurfaceProvider>
}

When you paste the contents of this clipboard, a copy of source will be created, a copy of target will be created, and a copy of edge - whose source and target are the new vertices - will also be created.


Note that the pasted items are always pasted to the same place in this example - because we use {origin:{x:50, y:250}} in our paste command. The section below on paste options discusses the paste origin and what options are available to you.



Pasting clipboard contents​

Use the paste method to paste the current contents of the clipboard.

paste​

Paste the clipboard's most recent entry, optionally removing it from the clipboard afterwards.
Signature
paste(options:BrowserUIPasteOptions)
Parameters
optionsBrowserUIPasteOptionsOptions for the paste.
Return value
ClonedSet
info

Paste origin​

A set of vertices has an implicit origin, which is computed as the x position of the leftmost vertex, and the y position of the topmost vertex. When you call paste without providing an origin, the new objects are placed on top of the existing object. When you call paste and provide an origin - as we did in the example above - the location of each vertex to be pasted is translated by the delta between the paste origin and the computed origin of the clipboard:

surface.clipboard.paste({origin:{x:50, y:250}})

Event as origin​

In real world use cases it is the user who generally decides where to paste content. The clipboard allows you to use a MouseEvent as the origin instead of specifying it yourself. At any point, the canvas for a given Surface has been panned in one or both axes, and is likely zoomed in or out to some value other than 1:1. When you provide a MouseEvent as the origin for a paste, the location of this event is automatically mapped to a location onto the Surface. In this next example, first press Copy Items to populate the clipboard. Then you can click anywhere on the canvas to paste the copied content:


The code for this is as shown here - note our EVENT_CANVAS_CLICK listener in the render options. We pass the event from that method into the paste method.

import { useRef } from "react"
import { SurfaceComponent, SurfaceComponentRef, SurfaceProvider } from "@visuallyjs/browser-ui-react"
import { EVENT_CANVAS_CLICK } from "@visuallyjs/browser-ui"

export default function MyComponent() {

const surfaceRef = useRef<SurfaceComponentRef>(null)

function addItemsToSelection() {
// ...
}

return <SurfaceProvider>
<SurfaceComponent ref={surfaceRef} data={data} renderOptions={{
events: {
[EVENT_CANVAS_CLICK]: (ui, event) => ui.clipboard.paste({event})
}
}}/>
<button onClick={() => addItemsToSelection()}>Copy items</button>
</SurfaceProvider>
}

Vertices not in clipboard​

In this snippet we create two nodes and an edge, and then we copy one of the nodes and the edge into the clipboard:


// ... import and create a clipboard

const source = model.addNode({id:"source"})
const target = model.addNode({id:"target"})
const edge = model.addEdge({source, target})

surface.clipboard.copy([source, edge]) // copy source node only and the edge

What happens now if we paste this?

clipboard.paste({event:someMouseEvent})

By default, the clipboard will create a copy of source, and a copy of edge. The cloned source will be the source for the cloned edge, but the target for the cloned edge will be the original target vertex. You can see that in this example:


This behaviour can be modified via the use of the hermetic flag.

clipboard.paste({event:someMouseEvent, hermetic:true})

In this call, hermetic:true instructs the clipboard to only paste edges for which both the source and target vertices were also in the clipboard. So in this case, only a clone of the source vertex will be pasted, and the edge will not be pasted. hermetic defaults to false.

Edge geometry​

An edge that has "geometry" attached to it is an edge that has either been loaded with a geometry section in the JSON, or has been edited by a connector editor, using the mouse, or fingers. When you copy an edge with geometry and subsequently paste it, the rules for the associated geometry are:

  • if you copy and paste the edge and both its source and target vertices, the pasted edge has geometry attached, the value for which is the original edge's geometry translated according to the transformation of the origin as discussed above.

  • if you copy and paste an edge but not both its source and target vertices, the pasted edge does not have geometry attached, and will be painted according to VisuallyJs's default algorithm for the specific connector.

Nested groups and nodes​

If you copy and paste a group that has child nodes or groups, the child nodes or groups will also be copied, and pasted as children of the pasted group. This mechanism works to an arbitrary level of nesting. If you wish to copy a group without any of its child content, you can specify a "shallow" paste, by setting shallow:true on the paste call:

clipboard.paste({event, shallow:true}) 

In this canvas we have copied the main group into the clipboard on load. When you left-click on the canvas, the group is pasted with all of its descendants at the point you clicked. When you right-click, the group is "shallow" pasted - only the group itself, not any of its descendants.


This is the code we used:

import { useRef, useEffect } from "react"
import { SurfaceComponent, SurfaceComponentRef, SurfaceProvider } from "@visuallyjs/browser-ui-react"
import {EVENT_CANVAS_CLICK, EVENT_CONTEXTMENU} from "@visuallyjs/browser-ui";

const shallowCopyData = {
groups:[
{id:"g1", left:50, top:10 },
{id:"g2", left:50, top:10, group:"g1" },
{id:"g3", left:50, top:10, group:"g2" }
],
nodes:[
{ id:"1", left:50, top:10, group:"g3" }
]
}

export default function MyComponent() {

const surfaceRef = useRef<SurfaceComponentRef>(null)

const renderOptions = {
events: {
// on left-click, paste a deep clone of the group
[EVENT_CANVAS_CLICK]: (ui, event) => ui.clipboard.paste({event}),
// on right-click, paste a shallow clone of the group
[EVENT_CONTEXTMENU]: (ui, event) => ui.clipboard.paste({event, shallow:true})
}
}

// on load we zoom out and center the group so there's some whitespace for you to click in.
// then we copy group `g1` to the clipboard.
useEffect(() => {
const s = surfaceRef.current.getSurface()
s.setZoom(0.4)
s.centerContent()
s.clipboard.copy(s.model.getGroup("g1"))
}, []);

return <SurfaceProvider>
<SurfaceComponent ref={surfaceRef} data={shallowCopyData} renderOptions={renderOptions}/>
</SurfaceProvider>
}

Pasting the current selection​

As discussed in the Selection docs, each VisuallyJs model maintains a list of currently selected objects, and various parts of the UI add/remove objects to/from the current selection. You can paste the current selection for some model via the pasteCurrentSelection method:

pasteCurrentSelection​

Copies and pastes the contents of the associated model instance's current selection into the clipboard. This method is equivalent to calling copyCurrentSelection() first and then calling paste(..).
Signature
pasteCurrentSelection(options:PasteOptions)
Parameters
optionsPasteOptionsOptions for the paste.
Return value
ClonedSet
import { SurfaceComponent } from "@visuallyjs/browser-ui-react"

import { EVENT_TAP, EVENT_CANVAS_CLICK } from "@visuallyjs/browser-ui"

export default function MyComponent() {

const renderOptions = {
events: {
[EVENT_CANVAS_CLICK]: (ui, event) => ui.clipboard.pasteCurrentSelection({event})
}
}

const viewOptions = {
nodes: {
default: {
events: {
[EVENT_TAP]: (p) => p.model.toggleSelection(p.obj)
}
}
}
}
return <SurfaceComponent renderOptions={renderOptions} viewOptions={viewOptions}/>
}

In this canvas, when you tap a vertex it will be added to the model's current selection. Subsequently clicking on the canvas will paste a copy of the vertices in the current selection at the location you clicked. To clear the selection, use the Clear selection button in the controls.


Clearing the clipboard​

The clipboard offers a clear method that will remove all copied content:

clipboard.clear()

You can also instruct the clipboard to clear the content that was just pasted:

clipboard.paste({origin:{x:50, y:50}, clear:true})