Skip to content

Commit

Permalink
feat(network): add the ability to control the center force strength
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Dec 31, 2021
1 parent 7fdff22 commit 609287d
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 48 deletions.
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -223,6 +223,7 @@ website: ##@2 website start website in dev mode

website-build: ##@2 website build website
@echo "${YELLOW}Building website${RESET}"
@-rm -rf website/.cache
@cd website && yarn build

website-serve: ##@2 website build & serve website
Expand Down
32 changes: 16 additions & 16 deletions packages/generators/src/network.ts
@@ -1,37 +1,37 @@
import random from 'lodash/random'

type Link = {
distance: number
source: string
target: string
distance: number
}

type ExtraNode = {
color: string
depth: number
id: string
radius: number
height: number
color: string
size: number
}

export const generateNetworkData = ({
rootNodeRadius = 12,
rootNodeSize = 24,
minMidNodes = 6,
maxMidNodes = 16,
midNodeRadius = 8,
midNodeSize = 16,
minLeaves = 4,
maxLeaves = 16,
leafRadius = 4,
leafSize = 8,
} = {}) => {
const rootNode = {
id: '0',
radius: rootNodeRadius,
depth: 0,
height: 2,
size: rootNodeSize,
color: 'rgb(244, 117, 96)',
}
let nodes = Array.from({ length: random(minMidNodes, maxMidNodes) }, (_, k) => ({
id: `${k + 1}`,
radius: midNodeRadius,
depth: 1,
height: 1,
size: midNodeSize,
color: 'rgb(97, 205, 187)',
}))

Expand All @@ -41,28 +41,28 @@ export const generateNetworkData = ({
links.push({
source: '0',
target: source.id,
distance: 50,
distance: 80,
})
nodes.forEach(target => {
if (Math.random() < 0.04) {
links.push({
source: source.id,
target: target.id,
distance: 70,
distance: 80,
})
}
})
Array.from({ length: random(minLeaves, maxLeaves) }, (_, k) => {
extraNodes.push({
id: `${source.id}.${k}`,
radius: leafRadius,
depth: 2,
height: 0,
size: leafSize,
color: 'rgb(232, 193, 160)',
})
links.push({
source: source.id,
target: `${source.id}.${k}`,
distance: 30,
distance: 50,
})

return null
Expand Down
2 changes: 2 additions & 0 deletions packages/network/src/Network.tsx
Expand Up @@ -21,6 +21,7 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
data: { nodes: rawNodes, links: rawLinks },

linkDistance = svgDefaultProps.linkDistance,
centeringStrength = svgDefaultProps.centeringStrength,
repulsivity = svgDefaultProps.repulsivity,
distanceMin = svgDefaultProps.distanceMin,
distanceMax = svgDefaultProps.distanceMax,
Expand Down Expand Up @@ -70,6 +71,7 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
nodes: rawNodes,
links: rawLinks,
linkDistance,
centeringStrength,
repulsivity,
distanceMin,
distanceMax,
Expand Down
2 changes: 2 additions & 0 deletions packages/network/src/NetworkCanvas.tsx
Expand Up @@ -19,6 +19,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
data: { nodes: rawNodes, links: rawLinks },

linkDistance = canvasDefaultProps.linkDistance,
centeringStrength = canvasDefaultProps.centeringStrength,
repulsivity = canvasDefaultProps.repulsivity,
distanceMin = canvasDefaultProps.distanceMin,
distanceMax = canvasDefaultProps.distanceMax,
Expand Down Expand Up @@ -54,6 +55,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
nodes: rawNodes,
links: rawLinks,
linkDistance,
centeringStrength,
repulsivity,
distanceMin,
distanceMax,
Expand Down
5 changes: 3 additions & 2 deletions packages/network/src/defaults.ts
Expand Up @@ -22,10 +22,11 @@ export const commonDefaultProps: Omit<
layers: ['links', 'nodes', 'annotations'],

linkDistance: 30,
repulsivity: 3,
centeringStrength: 1,
repulsivity: 10,
distanceMin: 1,
distanceMax: Infinity,
iterations: 160,
iterations: 120,

nodeSize: 12,
activeNodeSize: 18,
Expand Down
21 changes: 17 additions & 4 deletions packages/network/src/hooks.ts
Expand Up @@ -25,12 +25,14 @@ const useDerivedProp = <Target, Output extends string | number>(

const useComputeForces = <Node extends InputNode, Link extends InputLink>({
linkDistance,
centeringStrength,
repulsivity,
distanceMin,
distanceMax,
center,
}: {
linkDistance: NetworkCommonProps<Node, Link>['linkDistance']
centeringStrength: NetworkCommonProps<Node, Link>['centeringStrength']
repulsivity: NetworkCommonProps<Node, Link>['repulsivity']
distanceMin: NetworkCommonProps<Node, Link>['distanceMin']
distanceMax: NetworkCommonProps<Node, Link>['distanceMax']
Expand All @@ -42,9 +44,9 @@ const useComputeForces = <Node extends InputNode, Link extends InputLink>({
const centerY = center[1]

return useMemo(() => {
const linkForce = forceLink<TransientNode<Node>, TransientLink<Node, Link>>().distance(
link => getLinkDistance(link.data)
)
const linkForce = forceLink<TransientNode<Node>, TransientLink<Node, Link>>()
.distance(link => getLinkDistance(link.data))
.strength(centeringStrength)

const chargeForce = forceManyBody()
.strength(-repulsivity)
Expand All @@ -54,7 +56,15 @@ const useComputeForces = <Node extends InputNode, Link extends InputLink>({
const centerForce = forceCenter(centerX, centerY)

return { link: linkForce, charge: chargeForce, center: centerForce }
}, [getLinkDistance, repulsivity, distanceMin, distanceMax, centerX, centerY])
}, [
getLinkDistance,
centeringStrength,
repulsivity,
distanceMin,
distanceMax,
centerX,
centerY,
])
}

const useNodeStyle = <Node extends InputNode, Link extends InputLink>({
Expand Down Expand Up @@ -141,6 +151,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
nodes,
links,
linkDistance = commonDefaultProps.linkDistance,
centeringStrength = commonDefaultProps.centeringStrength,
repulsivity = commonDefaultProps.repulsivity,
distanceMin = commonDefaultProps.distanceMin,
distanceMax = commonDefaultProps.distanceMax,
Expand All @@ -159,6 +170,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
nodes: Node[]
links: Link[]
linkDistance?: NetworkCommonProps<Node, Link>['linkDistance']
centeringStrength?: NetworkCommonProps<Node, Link>['centeringStrength']
repulsivity?: NetworkCommonProps<Node, Link>['repulsivity']
distanceMin?: NetworkCommonProps<Node, Link>['distanceMin']
distanceMax?: NetworkCommonProps<Node, Link>['distanceMax']
Expand All @@ -180,6 +192,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu

const forces = useComputeForces<Node, Link>({
linkDistance,
centeringStrength,
repulsivity,
distanceMin,
distanceMax,
Expand Down
1 change: 1 addition & 0 deletions packages/network/src/types.ts
Expand Up @@ -139,6 +139,7 @@ export type NetworkCommonProps<Node extends InputNode, Link extends InputLink> =
margin: Box

linkDistance: DerivedProp<Link, number>
centeringStrength: number
repulsivity: number
distanceMin: number
distanceMax: number
Expand Down
28 changes: 19 additions & 9 deletions website/src/components/icons/NetworkIcon.tsx
Expand Up @@ -7,24 +7,30 @@ import networkDarkColoredImg from '../../assets/icons/network-dark-colored.png'
import { ICON_SIZE, Icon, colors, IconImg } from './styled'
import { IconType } from './types'

type Node = {
id: string
size: number
color: string
}

const getData = (currentColors: any) => {
let nodes = 'ABCDE'.split('').map(id => ({
id,
radius: 5,
size: 10,
color: currentColors[2],
}))
const links = nodes.map(node => ({
source: 'root',
target: node.id,
distance: 2,
distance: 30,
}))

const leaves: any[] = []
const leaves: Node[] = []
nodes.forEach(node => {
Array.from({ length: 7 }, (v, k) => {
leaves.push({
id: `${node.id}.${k}`,
radius: 3,
size: 6,
color: currentColors[4],
})
links.push({
Expand All @@ -36,26 +42,30 @@ const getData = (currentColors: any) => {
})

nodes = nodes.concat(leaves)
nodes.unshift({ id: 'root', radius: 11, color: currentColors[4] })
nodes.unshift({ id: 'root', size: 20, color: currentColors[4] })

return { nodes, links }
}

type Link = ReturnType<typeof getData>['links'][number]

const chartProps = {
width: ICON_SIZE,
height: ICON_SIZE,
linkDistance: (link: any) => link.distance,
repulsivity: 5,
linkDistance: (link: Link) => link.distance,
centeringStrength: 1.2,
repulsivity: 4,
linkThickness: 2,
nodeColor: (node: any) => node.color,
nodeSize: (node: Node) => node.size,
nodeColor: (node: Node) => node.color,
linkColor: { from: 'source.color' },
animate: false,
isInteractive: false,
}

const NetworkIconItem = ({ type }: { type: IconType }) => (
<Icon id={`network-${type}`} type={type}>
<Network {...chartProps} data={getData(colors[type].colors)} />
<Network<Node, Link> {...chartProps} data={getData(colors[type].colors)} />
</Icon>
)

Expand Down
10 changes: 5 additions & 5 deletions website/src/data/components/network/mapper.ts
@@ -1,28 +1,28 @@
import { settingsMapper } from '../../../lib/settings'

export const dynamicNodeSizeValue = 'dynamic: (node: InputNode) => node.radius * 2'
export const dynamicActiveNodeSizeValue = 'dynamic: (node: InputNode) => node.radius * 3'
export const dynamicNodeSizeValue = 'dynamic: (node: InputNode) => node.size'
export const dynamicActiveNodeSizeValue = 'dynamic: (node: InputNode) => node.size * 1.5'
export const dynamicLinkThicknessValue =
'dynamic: (link: ComputedLink) => (2 - link.source.data.depth) * 2'

export default settingsMapper({
nodeSize: (value: number | typeof dynamicNodeSizeValue) => {
if (value === dynamicNodeSizeValue) {
return (node: any) => node.radius * 2
return (node: any) => node.size
}

return value
},
activeNodeSize: (value: number | typeof dynamicActiveNodeSizeValue) => {
if (value === dynamicActiveNodeSizeValue) {
return (node: any) => node.radius * 3
return (node: any) => node.size * 1.5
}

return value
},
linkThickness: (value: number | typeof dynamicLinkThicknessValue) => {
if (value === dynamicLinkThicknessValue) {
return (link: any) => (2 - link.source.data.depth) * 2
return (link: any) => 2 + link.target.data.height * 2
}

return value
Expand Down
7 changes: 6 additions & 1 deletion website/src/data/components/network/meta.yml
Expand Up @@ -17,7 +17,12 @@ Network:
- label: Custom link component
link: network--custom-link
description: |
A network component connecting nodes with links.
A network component connecting nodes with links using various forces,
the resulting layout will depends on `linkDistance`, `centeringStrength`
and `repulsivity`, so you should play with those parameters in order
to achieve the desired result.
You can also add some extra constrains via `distanceMin` and `distanceMax`.
The responsive alternative of this component is `ResponsiveNetwork`.
Expand Down
25 changes: 21 additions & 4 deletions website/src/data/components/network/props.ts
Expand Up @@ -52,13 +52,30 @@ const props: ChartProperty[] = [
description: `
If you set a **number**, this value will be used for all links.
If you use a **string**, this will be used to pick the distance
from the corresponding link property, thus, this property
should exist on each link.
If you use a **function**, it will receive a link and must return
the desired distance.
Please note that in most cases you won't get links having the
exact distance you specified as it also depends on the other forces.
`,
},
{
key: 'centeringStrength',
group: 'Simulation',
type: 'number',
help: 'Control how much the centering force affects nodes positioning.',
description: `
This value will also affect the strength
of \`distanceMin\` and \`distanceMax\`.
`,
flavors: allFlavors,
defaultValue: defaults.centeringStrength,
control: {
type: 'range',
min: 0,
max: 2,
step: 0.1,
},
},
{
key: 'repulsivity',
Expand Down

0 comments on commit 609287d

Please sign in to comment.