Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
51bfc58
custom outer and inner nodes
parties Mar 15, 2020
f62e905
work on custom link
parties Mar 15, 2020
de78f8f
editable label, not connected to state though
parties Mar 15, 2020
93ac7fd
context with state working
parties Mar 15, 2020
e51f72e
fixed animation by force updating
parties Mar 15, 2020
ee0b753
editable node labels!
parties Mar 15, 2020
cf6cbc5
editable link labels
parties Mar 15, 2020
9978845
add color picker for nodes; enable coloration for links (but no picke…
parties Mar 15, 2020
fb93f1b
double click to add new node
parties Mar 15, 2020
4712d68
beginnings of link styling via classes; fix double click bug on links
parties Mar 16, 2020
05b25fb
deletable links
parties Mar 16, 2020
f8fd674
nodes have four ports now
parties Mar 16, 2020
1dfdd92
now using material-ui's TextField for node and link labels
parties Mar 16, 2020
00bf173
saves to local storage on change; small style updates; now user canno…
parties Mar 16, 2020
5454279
linting
parties Mar 16, 2020
89142bd
more lint fixes
parties Mar 16, 2020
caaf04c
refactored the chart context out into separate file; reverted CustomN…
parties Mar 16, 2020
61dd0be
fixed positioning when double clicking to add; adjust view to give wi…
parties Mar 16, 2020
d1ae41f
straight lines + arrows (with rotation)
parties Mar 17, 2020
53189f7
empty flow chart data at first; beginning of a toolbar component
parties Mar 17, 2020
51b540b
upgrade dependencies
parties Mar 18, 2020
3e15278
block dragging node on shift + click; hide delete button on links unt…
parties Mar 18, 2020
70a6385
small improvements to link styles
parties Mar 18, 2020
41dac81
add EditorContext; update toolbar; set default state to be parents ex…
parties Mar 18, 2020
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
24 changes: 24 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"editor.formatOnSave": false,
"editor.tabSize": 2,
"editor.detectIndentation": true,
"eslint.alwaysShowStatus": true,
"eslint.format.enable": true,
"files.trimFinalNewlines": true,
"[javascript]": {
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 2
},
"[typescriptreact]": {
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 2
},
}
15,708 changes: 6,169 additions & 9,539 deletions package-lock.json

Large diffs are not rendered by default.

64 changes: 34 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,59 @@
"name": "@mrblenny/react-flow-chart",
"version": "0.0.11",
"description": "A flexible, stateless flow chart library for react.",
"main": "src/index.js",
"main": "src/index",
"repository": "git@github.com:MrBlenny/react-flow-chart.git",
"author": "David Revay <daverevay@gmail.com>",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.1.2",
"@storybook/addon-centered": "^5.3.2",
"@storybook/addon-options": "5.3.2",
"@storybook/addon-viewport": "5.3.2",
"@storybook/react": "5.3.2",
"@types/lodash": "^4.14.118",
"@types/node": "10.12.0",
"@babel/core": "^7.8.7",
"@storybook/addon-centered": "^5.3.17",
"@storybook/addon-options": "5.3.17",
"@storybook/addon-viewport": "5.3.17",
"@storybook/react": "5.3.17",
"@types/lodash": "^4.14.149",
"@types/node": "13.9.1",
"@types/pathfinding": "0.0.4",
"@types/react": "^16.8.8",
"@types/react-dom": "^16.8.4",
"@types/styled-components": "^4.1.19",
"@types/uuid": "^3.4.4",
"@types/webpack": "4.4.17",
"babel-loader": "^8.0.4",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
"@types/styled-components": "^5.0.1",
"@types/uuid": "^7.0.2",
"@types/webpack": "4.41.7",
"babel-loader": "^8.0.6",
"lodash": "^4.17.15",
"np": "^3.0.4",
"react": "^16.8.4",
"np": "^6.2.0",
"react": "^16.13.0",
"react-dom": "^16.8.5",
"react-json-view": "^1.19.1",
"ts-loader": "^5.2.2",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-config-standard": "^8.0.1",
"tslint-react": "^3.6.0",
"typescript": "^3.4.1",
"webpack": "^4.21.0",
"webpack-cli": "^3.1.2"
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"tslint": "^6.1.0",
"tslint-config-standard": "^9.0.0",
"tslint-react": "^4.2.0",
"typescript": "^3.8.3",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"@material-ui/core": "^4.9.5",
"pathfinding": "^0.4.18",
"react-draggable": "^3.0.5",
"react-color": "^2.18.0",
"react-draggable": "^4.2.0",
"react-resize-observer": "^1.1.1",
"styled-components": "^5.0.0",
"uuid": "^3.3.2"
"styled-components": "^5.0.1",
"typeface-roboto": "0.0.75",
"uuid": "^7.0.2"
},
"peerDependencies": {
"react": "^16.8.4",
"react-dom": "^16.8.4"
"react": "^16.13.0",
"react-dom": "^16.13.0"
},
"scripts": {
"start:storybook": "start-storybook -p 6006 -c config/storybook",
"build:storybook": "build-storybook -c config/storybook -o docs",
"build": "tsc && cp package.json dist/ && cp README.md ./dist",
"test": "npm run lint",
"lint": "tslint -p tsconfig.json -c tslint.json"
"lint": "tslint -p tsconfig.json -c tslint.json",
"lint:fix": "npm run lint -- --fix"
}
}
23 changes: 14 additions & 9 deletions src/components/Canvas/Canvas.wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import Draggable from 'react-draggable'
import { IConfig, IOnCanvasClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas, IOnDragCanvasStop, REACT_FLOW_CHART } from '../../'
import { IConfig, IOnCanvasClick, IOnCanvasDoubleClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas, IOnDragCanvasStop, REACT_FLOW_CHART } from '../../'
import CanvasContext from './CanvasContext'
import { ICanvasInnerDefaultProps } from './CanvasInner.default'
import { ICanvasOuterDefaultProps } from './CanvasOuter.default'
Expand All @@ -15,6 +15,7 @@ export interface ICanvasWrapperProps {
onDragCanvasStop: IOnDragCanvasStop
onDeleteKey: IOnDeleteKey
onCanvasClick: IOnCanvasClick
onCanvasDoubleClick: IOnCanvasDoubleClick
onCanvasDrop: IOnCanvasDrop
ComponentInner: React.FunctionComponent<ICanvasInnerDefaultProps>
ComponentOuter: React.FunctionComponent<ICanvasOuterDefaultProps>
Expand Down Expand Up @@ -72,6 +73,7 @@ export class CanvasWrapper extends React.Component<ICanvasWrapperProps, IState>
onDragCanvasStop,
children,
onCanvasClick,
onCanvasDoubleClick,
onDeleteKey,
onCanvasDrop,
} = this.props
Expand All @@ -94,22 +96,25 @@ export class CanvasWrapper extends React.Component<ICanvasWrapperProps, IState>
config={config}
children={children}
onClick={onCanvasClick}
onDoubleClick={onCanvasDoubleClick}
tabIndex={0}
onKeyDown={ (e: React.KeyboardEvent) => {
onKeyDown={(e: React.KeyboardEvent) => {
// delete or backspace keys
if (e.keyCode === 46 || e.keyCode === 8) {
if (e.keyCode === 46) {
onDeleteKey({ config })
}
}}
onDrop={ (e) => {
onDrop={(e) => {
const data = JSON.parse(e.dataTransfer.getData(REACT_FLOW_CHART))
if (data) {
onCanvasDrop({ data, position: {
x: e.clientX - (position.x + offsetX),
y: e.clientY - (position.y + offsetY),
}})
onCanvasDrop({
data, position: {
x: e.clientX - (position.x + offsetX),
y: e.clientY - (position.y + offsetY),
}
})
}
} }
}}
onDragOver={(e) => e.preventDefault()}
/>
</Draggable>
Expand Down
3 changes: 2 additions & 1 deletion src/components/Canvas/CanvasInner.default.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import styled from 'styled-components'
import { IConfig, IOnCanvasClick } from '../../'
import { IConfig, IOnCanvasClick, IOnCanvasDoubleClick } from '../../index'

export interface ICanvasInnerDefaultProps {
config: IConfig
children: any
onClick: IOnCanvasClick
onDoubleClick: IOnCanvasDoubleClick
tabIndex: number
onKeyDown: (e: React.KeyboardEvent) => void
onDrop: (e: React.DragEvent<HTMLDivElement>) => void
Expand Down
6 changes: 4 additions & 2 deletions src/components/FlowChart/FlowChart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import {
CanvasInnerDefault, CanvasOuterDefault, CanvasWrapper, ICanvasInnerDefaultProps, ICanvasOuterDefaultProps, IChart, IConfig, ILink,
ILinkDefaultProps, INodeDefaultProps, INodeInnerDefaultProps, IOnCanvasClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas,
ILinkDefaultProps, INodeDefaultProps, INodeInnerDefaultProps, IOnCanvasClick, IOnCanvasDoubleClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas,
IOnDragCanvasStop, IOnDragNode, IOnDragNodeStop, IOnLinkCancel, IOnLinkClick, IOnLinkComplete, IOnLinkMouseEnter,
IOnLinkMouseLeave, IOnLinkMove, IOnLinkStart, IOnNodeClick, IOnNodeMouseEnter, IOnNodeMouseLeave, IOnNodeSizeChange,
IOnPortPositionChange, IPortDefaultProps, IPortsDefaultProps, ISelectedOrHovered, LinkDefault, LinkWrapper, NodeDefault, NodeInnerDefault, NodeWrapper, PortDefault, PortsDefault,
Expand All @@ -23,6 +23,7 @@ export interface IFlowChartCallbacks {
onLinkMouseLeave: IOnLinkMouseLeave
onLinkClick: IOnLinkClick
onCanvasClick: IOnCanvasClick
onCanvasDoubleClick: IOnCanvasDoubleClick
onDeleteKey: IOnDeleteKey
onNodeClick: IOnNodeClick
onNodeMouseEnter: IOnNodeMouseEnter
Expand Down Expand Up @@ -81,6 +82,7 @@ export const FlowChart = (props: IFlowChartProps) => {
onLinkMouseLeave,
onLinkClick,
onCanvasClick,
onCanvasDoubleClick,
onDeleteKey,
onNodeClick,
onNodeMouseEnter,
Expand All @@ -100,7 +102,7 @@ export const FlowChart = (props: IFlowChartProps) => {
} = props
const { links, nodes, selected, hovered, offset } = chart

const canvasCallbacks = { onDragCanvas, onDragCanvasStop, onCanvasClick, onDeleteKey, onCanvasDrop }
const canvasCallbacks = { onDragCanvas, onDragCanvasStop, onCanvasClick, onCanvasDoubleClick, onDeleteKey, onCanvasDrop }
const linkCallbacks = { onLinkMouseEnter, onLinkMouseLeave, onLinkClick }
const nodeCallbacks = { onDragNode, onNodeClick, onDragNodeStop, onNodeMouseEnter, onNodeMouseLeave, onNodeSizeChange }
const portCallbacks = { onPortPositionChange, onLinkStart, onLinkMove, onLinkComplete, onLinkCancel }
Expand Down
1 change: 1 addition & 0 deletions src/components/FlowChart/utils/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const getEmptyMatrix = (width: number, height: number): number[][] => {
const matrix = []

for (let i = 0; i < adjustedHeight; i++) {
// @ts-ignore
matrix.push(new Array(adjustedWidth).fill(0))
}

Expand Down
59 changes: 43 additions & 16 deletions src/components/Link/Link.default.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { generateCurvePath, generateRightAnglePath, generateSmartPath, IConfig, ILink, IOnLinkClick, IOnLinkMouseEnter, IOnLinkMouseLeave, IPort, IPosition } from '../../'
import { generateCurvePath, generateRightAnglePath, generateSmartPath, IConfig, ILink, IOnLinkClick, IOnLinkMouseEnter, IOnLinkMouseLeave, IPort, IPosition, generateStraightPath, generateLoopBackPath } from '../../'

export interface ILinkDefaultProps {
config: IConfig
Expand All @@ -14,9 +14,10 @@ export interface ILinkDefaultProps {
isHovered: boolean
isSelected: boolean
matrix?: number[][]
className?: string
}

export const LinkDefault = ({
export function LinkDefault({
config,
link,
startPos,
Expand All @@ -29,31 +30,57 @@ export const LinkDefault = ({
isHovered,
isSelected,
matrix,
}: ILinkDefaultProps) => {
className = '',
}: ILinkDefaultProps) {

const points = config.smartRouting ?
!!toPort && !!matrix ? generateSmartPath(matrix, startPos, endPos, fromPort, toPort) : generateRightAnglePath(startPos, endPos)
: generateCurvePath(startPos, endPos)
// const points = config.smartRouting ?
// !!toPort && !!matrix ? generateSmartPath(matrix, startPos, endPos, fromPort, toPort) : generateRightAnglePath(startPos, endPos)
// : generateCurvePath(startPos, endPos)

let points
if (link.from.nodeId === link.to.nodeId) {
points = generateLoopBackPath(startPos, endPos)
} else {
points = generateStraightPath(startPos, endPos)
}

const linkColor: string = (fromPort.properties && fromPort.properties.linkColor) || 'cornflowerblue'

return (
<svg style={{ overflow: 'visible', position: 'absolute', cursor: 'pointer', left: 0, right: 0 }}>
<circle
<svg className={className} style={{ overflow: 'visible', position: 'absolute', cursor: 'pointer', left: 0, right: 0 }}>
<defs>
<marker
id="arrow"
viewBox="0 0 10 10"
refX="9"
refY="3"
markerWidth="20"
markerHeight="20"
fill={linkColor}
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 L 10 3 L 0 6 z" />
</marker>
</defs>
{/*<circle
r="4"
cx={startPos.x}
cy={startPos.y}
fill="cornflowerblue"
/>
fill={linkColor}
/>*/}
{/* Main line */}
<path
d={points}
stroke="cornflowerblue"
stroke={linkColor}
strokeWidth="3"
fill="none"
markerEnd="url(#arrow)"
/>
{/* Thick line to make selection easier */}
<path
d={points}
stroke="cornflowerblue"
stroke={linkColor}
strokeWidth="20"
fill="none"
strokeLinecap="round"
Expand All @@ -63,14 +90,14 @@ export const LinkDefault = ({
onClick={(e) => {
onLinkClick({ config, linkId: link.id })
e.stopPropagation()
} }
}}
/>
<circle
{/*<circle
r="4"
cx={endPos.x}
cy={endPos.y}
fill="cornflowerblue"
/>
fill={linkColor}
/>*/}
</svg>
)
}
4 changes: 2 additions & 2 deletions src/components/Link/Link.wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface ILinkWrapperProps {
matrix?: number[][]
}

export const LinkWrapper = React.memo(({
export const LinkWrapper = React.memo(function _LinkWrapper ({
config,
Component = LinkDefault,
link,
Expand All @@ -30,7 +30,7 @@ export const LinkWrapper = React.memo(({
fromNode,
toNode,
matrix,
}: ILinkWrapperProps) => {
}: ILinkWrapperProps) {
const startPos = getLinkPosition(fromNode, link.from.portId)
const fromPort = fromNode.ports[link.from.portId]

Expand Down
34 changes: 25 additions & 9 deletions src/components/Link/utils/generateCurvePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,38 @@ import * as PF from 'pathfinding'
import { IPort, IPosition } from '../../../'
import { MATRIX_PADDING } from '../../FlowChart/utils/grid'

export const generateStraightPath = (startPos: IPosition, endPos: IPosition): string => {
return `M ${startPos.x} ${startPos.y} L ${endPos.x} ${endPos.y}`
}

const scalar = 200;
export const generateLoopBackPath = (startPos: IPosition, endPos: IPosition): string => {
// working for top to right port
// need to generalize
return `
M ${startPos.x} ${startPos.y}
C ${(startPos.x < endPos.x) ? (startPos.x - scalar) : (startPos.x)},${(startPos.y < endPos.y) ? (startPos.y - scalar) : (startPos.y + scalar)}
${endPos.x + scalar},${endPos.y}
${endPos.x},${endPos.y}
`
}

export const generateCurvePath = (startPos: IPosition, endPos: IPosition): string => {
const width = Math.abs(startPos.x - endPos.x)
const height = Math.abs(startPos.y - endPos.y)
const leftToRight = startPos.x < endPos.x
const topToBottom = startPos.y < endPos.y
const isHorizontal = width > height

let start: IPosition
let end: IPosition
if (isHorizontal) {
start = leftToRight ? startPos : endPos
end = leftToRight ? endPos : startPos
} else {
start = topToBottom ? startPos : endPos
end = topToBottom ? endPos : startPos
}
let start: IPosition = startPos
let end: IPosition = endPos
// if (isHorizontal) {
// start = leftToRight ? startPos : endPos
// end = leftToRight ? endPos : startPos
// } else {
// start = topToBottom ? startPos : endPos
// end = topToBottom ? endPos : startPos
// }

const curve = isHorizontal ? width / 3 : height / 3
const curveX = isHorizontal ? curve : 0
Expand Down
Loading