Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding pannable and zoomable canvas component #100

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions @types/Canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { memo, ReactElement } from 'react';

export type PanState = { x: number, y: number };

export type CanvasProps = {
/**
* Since Canvas is a controlled component, the 'pan' prop defines the canvas panning
*/
pan?: PanState,
/**
* Since Canvas is a controlled component, the 'onPanChange' prop is the change handler of the 'pan' prop
*/
onPanChange?: (panState: PanState) => unknown,
/**
* Since Canvas is a controlled component, the 'zoom' prop defines its zoom level, aka: how much the canvas is scaling
*/
zoom?: number,
/**
* Since Canvas is a controlled component, the 'onZoomChange' prop is the change handler of the 'zoom' prop
*/
onZoomChange?: (zoom: number) => unknown,
/**
* Allow to zoom in/out on mouse wheel
*/
zoomOnWheel?: boolean,
/**
* The maximum allowed zoom
*/
maxZoom?: number,
/**
* The minimum allowed zoom
*/
minZoom?: number,
/**
* Defines whether the zoom should be reset on double click
*/
zoomResetOnDblClick?: boolean,
/**
* Defines whether the canvas should apply inertia when the drag is over
*/
inertia?: boolean,
/**
* Displays debug info
*/
debug?: boolean,
GridRenderer?: ReactElement,
ElementRenderer?: ReactElement,
}


declare const Canvas: (props: CanvasProps) => JSX.Element;

export default memo(Canvas);
20 changes: 20 additions & 0 deletions @types/CanvasControls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { memo, ElementType } from 'react';
import { PanState } from './Canvas';


export type CanvasControlsProps = {
placement?: 'top-left' | 'top-right' | 'top-center' | 'bottom-right' | 'bottom-center' | 'bottom-left' | 'left' | 'right',
alignment?: 'vertical' | 'horizontal',
onPanChange?: (panState: PanState) => unknown,
onZoomChange?: (zoom: PanState) => unknown,
ButtonRender?: ElementType,
ZoomInBtnRender?: ElementType,
CenterBtnRender?: ElementType,
ZoomOutBtnRender?: ElementType,
ElementRender?: ElementType,
}


declare const CanvasControls: (props: CanvasControlsProps) => JSX.Element;

export default memo(CanvasControls);
16 changes: 16 additions & 0 deletions @types/useCanvasState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { PanState } from './Canvas';


export type CanvasMethods = {
onPanChange: (panState: PanState) => unknown,
onZoomChange: (zoom: number) => unknown,
}

export type CanvasStates = {
pan: PanState,
zoom: number,
}

declare const useCanvasState: (initialStates?: CanvasStates) => [CanvasStates, CanvasMethods];

export default useCanvasState;
21 changes: 18 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.5.0] - 2020-11-20

### Added
### Added

- First implementation of draggable canvas
- First implementation of zoomable canvas


## [0.5.1] - 2020-11-25

### Added

- First implementation of draggable canvas
- First implementation of zoomable canvas
- Added `disconnect` function exported in `useSchema` hook

## [0.5.1] - 2020-11-27

Expand All @@ -142,3 +149,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reverted changes in `0.5.0` related to draggable canvas and zoomable
canvas due to an uncaught bug. We will continue working on these features
and release them in an upcoming version. Apologies everyone!

## [0.6.0] - 2020-12-12

### Added

- Canvas Component for panning and zooming
- useCanvas hook
- CanvasControl component
55 changes: 55 additions & 0 deletions docs/Basic-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
To start using the library import a `Canvas` and a `Diagram` component, both are [controlled components](https://reactjs.org/docs/forms.html#controlled-components)
so you'll need to provide a [state](https://reactjs.org/docs/faq-state.html) and a [state handler](https://reactjs.org/docs/faq-state.html#how-do-i-update-state-with-values-that-depend-on-the-current-state)
(*beautiful-react-diagrams* exports mainly controlled components).

A *Diagram* component needs to be wrapped into a *Canvas* which allows panning/zooming functionality.<br />

A *Diagram* can easily be represented by a "*schema*" (the library provides a set of pre-made utilities to define and validate schemas).
A "*schema*" is a plain object having, at least, a "*nodes*" property defined.<br />

The "*nodes*" property must be an array of tuples (objects) described by a unique "*id*" (if not provided the library will create a unique id for the node),
a "*content*" property (can be a React component) and a "*coordinates*" property describing the node position.

Optionally a "*links*" property can be defined to define links between the nodes, similar to the "*nodes*" property it must
be an array of valid link describing tuples, a valid link must have an "*input*" and an "*output*" property.

In order to avoid unnecessary complexity the `useSchema`, `useCanvasState` hooks have been provided together with the
`createSchema` utility.

```js
import Diagram, { Canvas, createSchema, useSchema, useCanvasState, CanvasControls } from 'beautiful-react-diagrams';

// the diagram model
const initialSchema = createSchema({
nodes: [
{ id: 'node-1', content: 'Hey Jude', coordinates: [312, 27], },
{ id: 'node-2', content: 'Don\'t', coordinates: [330, 90], },
{ id: 'node-3', content: 'be afraid', coordinates: [100, 320], },
{ id: 'node-4', content: 'let me down', coordinates: [306, 332], },
{ id: 'node-5', content: 'make it bad', coordinates: [515, 330], },
],
links: [
{ input: 'node-1', output: 'node-2' },
{ input: 'node-2', output: 'node-3' },
{ input: 'node-2', output: 'node-4' },
{ input: 'node-2', output: 'node-5' },
]
});

const DiagramExample = () => {
const [canvasState, handlers] = useCanvasState(); // creates canvas state
const [schema, { onChange }] = useSchema(initialSchema); // creates diagrams schema

return (
<div style={{ height: '30rem' }}>
<Canvas {...canvasState} {...handlers}>
<Diagram schema={schema} onChange={onChange} />
<CanvasControls />
</Canvas>
</div>
);
};

<DiagramExample />
```

36 changes: 36 additions & 0 deletions docs/CanvasControls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
```js
import Diagram, { Canvas, createSchema, useSchema, useCanvasState, CanvasControls } from 'beautiful-react-diagrams';

// the diagram model
const initialSchema = createSchema({
nodes: [
{ id: 'node-1', content: 'Hey Jude', coordinates: [312, 27], },
{ id: 'node-2', content: 'Don\'t', coordinates: [330, 90], },
{ id: 'node-3', content: 'be afraid', coordinates: [100, 320], },
{ id: 'node-4', content: 'let me down', coordinates: [306, 332], },
{ id: 'node-5', content: 'make it bad', coordinates: [515, 330], },
],
links: [
{ input: 'node-1', output: 'node-2' },
{ input: 'node-2', output: 'node-3' },
{ input: 'node-2', output: 'node-4' },
{ input: 'node-2', output: 'node-5' },
]
});

const DiagramExample = () => {
const [canvasState, handlers] = useCanvasState(); // creates canvas state
const [schema, { onChange }] = useSchema(initialSchema); // creates diagrams schema

return (
<div style={{ height: '30rem' }}>
<Canvas {...canvasState} {...handlers}>
<Diagram schema={schema} onChange={onChange} />
<CanvasControls alignment="horizontal" placement="bottom-center" />
</Canvas>
</div>
);
};

<DiagramExample />
```
120 changes: 120 additions & 0 deletions docs/Links-Ports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
### Ports

```js
import Diagram, { Canvas, createSchema, useSchema, useCanvasState, CanvasControls } from 'beautiful-react-diagrams';

const initialSchema = createSchema({
nodes: [
{
id: 'node-1',
content: 'Start',
coordinates: [100, 150],
outputs: [
{ id: 'port-1', alignment: 'right' },
{ id: 'port-2', alignment: 'right' },
],
disableDrag: true,
data: {
foo: 'bar',
count: 0,
}
},
{
id: 'node-2',
content: 'Middle',
coordinates: [300, 150],
inputs: [
{ id: 'port-3', alignment: 'left' },
{ id: 'port-4', alignment: 'left' },
],
outputs: [
{ id: 'port-5', alignment: 'right' },
{ id: 'port-6', alignment: 'right' },
],
data: {
bar: 'foo',
}
},
{
id: 'node-3',
content: 'End',
coordinates: [600, 150],
inputs: [
{ id: 'port-7', alignment: 'left' },
{ id: 'port-8', alignment: 'left' },
],
data: {
foo: true,
bar: false,
some: {
deep: {
object: true,
}
},
}
},
],
links: [
{ input: 'port-1', output: 'port-4' },
]
});

const UncontrolledDiagram = () => {
const [canvasState, handlers] = useCanvasState(); // creates canvas state
const [schema, { onChange }] = useSchema(initialSchema); // creates diagrams schema

return (
<div style={{ height: '30rem' }}>
<Canvas {...canvasState} {...handlers}>
<Diagram schema={schema} onChange={onChange} />
<CanvasControls />
</Canvas>
</div>
);
};

<UncontrolledDiagram />
```

### Readonly Links

```js static
import Diagram, { Canvas, createSchema, useSchema, useCanvasState, CanvasControls } from 'beautiful-react-diagrams';

// the diagram model
const initialSchema = createSchema({
nodes: [
{ id: 'node-1', content: 'Hey Jude', coordinates: [312, 27], },
{ id: 'node-2', content: 'Don\'t', coordinates: [330, 90], },
{ id: 'node-3', content: 'be afraid', coordinates: [100, 320], },
{ id: 'node-4', content: 'let me down', coordinates: [306, 332], },
{ id: 'node-5', content: 'make it bad', coordinates: [515, 330], },
{ id: 'node-6', content: 'Take a sad song', coordinates: [295, 460], },
],
links: [
{ input: 'node-1', output: 'node-2', readonly: true, className: 'my-custom-link-class' },
{ input: 'node-2', output: 'node-3', readonly: true },
{ input: 'node-2', output: 'node-4', readonly: true },
{ input: 'node-2', output: 'node-5', readonly: true },
{ input: 'node-3', output: 'node-6', readonly: true },
{ input: 'node-4', output: 'node-6', readonly: true },
{ input: 'node-5', output: 'node-6', readonly: true },
]
});

const DiagramExample = () => {
const [canvasState, handlers] = useCanvasState(); // creates canvas state
const [schema, { onChange }] = useSchema(initialSchema); // creates diagrams schema

return (
<div style={{ height: '35rem' }}>
<Canvas {...canvasState} {...handlers}>
<Diagram schema={schema} onChange={onChange} />
<CanvasControls />
</Canvas>
</div>
);
};

<DiagramExample />
```
2 changes: 1 addition & 1 deletion docs/schema.md → docs/Schema-utils.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Managing complex and large schemas could be a problem, for this reason a set of function to handle the schema object
Managing complex and large schemas could be a hard thing to do, for this reason a set of function to handle the schema
comes with the library.

### createSchema
Expand Down
39 changes: 0 additions & 39 deletions docs/basic-usage.md

This file was deleted.

Loading