Skip to content

Commit

Permalink
Merge pull request #145 from davidanitoiu/feature/arrowHeads
Browse files Browse the repository at this point in the history
feature/show-arrow-heads
  • Loading branch information
MrBlenny committed Jun 28, 2020
2 parents 93f8731 + 4a637f4 commit bba4b3b
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 63 deletions.
78 changes: 21 additions & 57 deletions src/components/Link/Link.default.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import { generateCurvePath, generateRightAnglePath, generateSmartPath, IConfig, ILink, IOnLinkClick, IOnLinkMouseEnter, IOnLinkMouseLeave, IPort, IPosition } from '../../'
import { ArrowLink, RegularLink } from './variants'

export interface ILinkDefaultProps {
className?: string
Expand All @@ -17,64 +18,27 @@ export interface ILinkDefaultProps {
matrix?: number[][]
}

export const LinkDefault = ({
className,
config,
link,
startPos,
endPos,
fromPort,
toPort,
onLinkMouseEnter,
onLinkMouseLeave,
onLinkClick,
isHovered,
isSelected,
matrix,
}: ILinkDefaultProps) => {

const points = config.smartRouting ?
!!toPort && !!matrix ? generateSmartPath(matrix, startPos, endPos, fromPort, toPort) : generateRightAnglePath(startPos, endPos)
export const LinkDefault = (props: ILinkDefaultProps) => {
const { config, startPos, endPos, fromPort, toPort, matrix } = props
const points = config.smartRouting
? !!toPort && !!matrix
? generateSmartPath(matrix, startPos, endPos, fromPort, toPort)
: generateRightAnglePath(startPos, endPos)
: generateCurvePath(startPos, endPos)

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

const linkProps = {
config,
points,
linkColor,
startPos,
endPos,
...props,
}

return (
<svg style={{ overflow: 'visible', position: 'absolute', cursor: 'pointer', left: 0, right: 0 }} className={className}>
<circle
r="4"
cx={startPos.x}
cy={startPos.y}
fill={linkColor}
/>
{/* Main line */}
<path
d={points}
stroke={linkColor}
strokeWidth="3"
fill="none"
/>
{/* Thick line to make selection easier */}
<path
d={points}
stroke={linkColor}
strokeWidth="20"
fill="none"
strokeLinecap="round"
strokeOpacity={(isHovered || isSelected) ? 0.1 : 0}
onMouseEnter={() => onLinkMouseEnter({ config, linkId: link.id })}
onMouseLeave={() => onLinkMouseLeave({ config, linkId: link.id })}
onClick={(e) => {
onLinkClick({ config, linkId: link.id })
e.stopPropagation()
} }
/>
<circle
r="4"
cx={endPos.x}
cy={endPos.y}
fill={linkColor}
/>
</svg>
)
return config.showArrowHead
? <ArrowLink {...linkProps} />
: <RegularLink {...linkProps} />
}
14 changes: 8 additions & 6 deletions src/components/Link/utils/generateCurvePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import * as PF from 'pathfinding'
import { IPort, IPosition } from '../../../'
import { MATRIX_PADDING } from '../../FlowChart/utils/grid'

export const generateCurvePath = (startPos: IPosition, endPos: IPosition): string => {
export const getDirectional = (startPos: IPosition, endPos: IPosition) => {
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

return { width, height,leftToRight,topToBottom,isHorizontal }
}

export const generateCurvePath = (startPos: IPosition, endPos: IPosition): string => {
const { width, height,leftToRight,topToBottom,isHorizontal } = getDirectional(startPos,endPos)

let start: IPosition
let end: IPosition
if (isHorizontal) {
Expand All @@ -32,11 +38,7 @@ const finder = PF.JumpPointFinder({
})

export const generateRightAnglePath = (startPos: IPosition, endPos: IPosition) => {
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
const { leftToRight,topToBottom,isHorizontal } = getDirectional(startPos,endPos)

let start: IPosition
let end: IPosition
Expand Down
93 changes: 93 additions & 0 deletions src/components/Link/variants/ArrowLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as React from 'react'
import { IConfig, ILink, IOnLinkClick, IOnLinkMouseEnter, IOnLinkMouseLeave, IPosition } from '../../../'
import { getDirectional } from '../utils'

export interface IArrowLinkProps {
link: ILink
config: IConfig
linkColor: string
points: string
isHovered: boolean
isSelected: boolean
startPos: IPosition
endPos: IPosition
onLinkMouseEnter: IOnLinkMouseEnter
onLinkMouseLeave: IOnLinkMouseLeave
onLinkClick: IOnLinkClick
}

export const ArrowLink = ({
link,
config,
linkColor,
points,
isHovered,
isSelected,
startPos,
endPos,
onLinkMouseEnter,
onLinkMouseLeave,
onLinkClick,
}: IArrowLinkProps) => {
const { leftToRight, topToBottom, isHorizontal } = getDirectional(
startPos,
endPos,
)

let markerKey = ''
if ((leftToRight && isHorizontal) || (topToBottom && !isHorizontal)) {
markerKey = 'markerEnd'
} else if ((!leftToRight && isHorizontal) || !isHorizontal) {
markerKey = 'markerStart'
}

const marker = { [markerKey]: 'url(#arrowHead)' }

return (
<svg
style={{
overflow: 'visible',
position: 'absolute',
cursor: 'pointer',
left: 0,
right: 0,
}}
>
<defs>
<marker
id="arrowHead"
orient="auto-start-reverse"
markerWidth="2"
markerHeight="4"
refX="0.1"
refY="2"
>
<path d="M0,0 V4 L2,2 Z" fill={linkColor} />
</marker>
</defs>
{/* Main line */}
<path
d={points}
stroke={linkColor}
strokeWidth="3"
fill="none"
{...marker}
/>
{/* Thick line to make selection easier */}
<path
d={points}
stroke={linkColor}
strokeWidth="20"
fill="none"
strokeLinecap="round"
strokeOpacity={isHovered || isSelected ? 0.1 : 0}
onMouseEnter={() => onLinkMouseEnter({ config, linkId: link.id })}
onMouseLeave={() => onLinkMouseLeave({ config, linkId: link.id })}
onClick={(e) => {
onLinkClick({ config, linkId: link.id })
e.stopPropagation()
}}
/>
</svg>
)
}
62 changes: 62 additions & 0 deletions src/components/Link/variants/RegularLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react'
import { IConfig, ILink, IOnLinkClick, IOnLinkMouseEnter, IOnLinkMouseLeave, IPosition } from '../../../'

export interface IRegularLinkProps {
points: string
linkColor: string
config: IConfig
link: ILink
startPos: IPosition
endPos: IPosition
onLinkMouseEnter: IOnLinkMouseEnter
onLinkMouseLeave: IOnLinkMouseLeave
onLinkClick: IOnLinkClick
isHovered: boolean
isSelected: boolean
}

export const RegularLink = ({
points,
linkColor,
config,
link,
startPos,
endPos,
onLinkMouseEnter,
onLinkMouseLeave,
onLinkClick,
isHovered,
isSelected,
}: IRegularLinkProps) => {
return (
<svg
style={{
overflow: 'visible',
position: 'absolute',
cursor: 'pointer',
left: 0,
right: 0,
}}
>
<circle r="4" cx={startPos.x} cy={startPos.y} fill={linkColor} />
{/* Main line */}
<path d={points} stroke={linkColor} strokeWidth="3" fill="none" />
{/* Thick line to make selection easier */}
<path
d={points}
stroke={linkColor}
strokeWidth="20"
fill="none"
strokeLinecap="round"
strokeOpacity={isHovered || isSelected ? 0.1 : 0}
onMouseEnter={() => onLinkMouseEnter({ config, linkId: link.id })}
onMouseLeave={() => onLinkMouseLeave({ config, linkId: link.id })}
onClick={(e) => {
onLinkClick({ config, linkId: link.id })
e.stopPropagation()
}}
/>
<circle r="4" cx={endPos.x} cy={endPos.y} fill={linkColor} />
</svg>
)
}
2 changes: 2 additions & 0 deletions src/components/Link/variants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './RegularLink'
export * from './ArrowLink'
1 change: 1 addition & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface IConfig {
readonly?: boolean
snapToGrid?: boolean
smartRouting?: boolean
showArrowHead?: boolean
gridSize?: number
validateLink?: (props: IOnLinkCompleteInput & { chart: IChart }) => boolean
nodeProps?: any
Expand Down
27 changes: 27 additions & 0 deletions stories/LinkWithArrowHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { cloneDeep, mapValues } from 'lodash'
import * as React from 'react'
import { FlowChart } from '../src'
import * as actions from '../src/container/actions'
import { Page } from './components'
import { chartSimple } from './misc/exampleChartState'

export class LinkWithArrowHead extends React.Component {
public state = cloneDeep(chartSimple)
public render () {
const chart = this.state
const stateActions = mapValues(actions, (func: any) =>
(...args: any) => this.setState(func(...args))) as typeof actions

return (
<Page>
<FlowChart
chart={chart}
callbacks={stateActions}
config={{
showArrowHead: true,
}}
/>
</Page>
)
}
}
2 changes: 2 additions & 0 deletions stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DragAndDropSidebar } from './DragAndDropSidebar'
import { ExternalReactState } from './ExternalReactState'
import { InternalReactState } from './InternalReactState'
import { LinkColors } from './LinkColors'
import { LinkWithArrowHead } from './LinkWithArrowHead'
import { ReadonlyMode } from './ReadonlyMode'
import { SelectedSidebar } from './SelectedSidebar'
import { SmartRouting } from './SmartRouting'
Expand All @@ -29,6 +30,7 @@ storiesOf('Custom Components', module)
.add('Canvas Outer', CustomCanvasOuterDemo)
.add('Canvas Link', () => <CustomLinkDemo />)
.add('Link Colors', () => <LinkColors />)
.add('ArrowHead',() => <LinkWithArrowHead />)

storiesOf('Stress Testing', module).add('default', StressTestDemo)

Expand Down

0 comments on commit bba4b3b

Please sign in to comment.