stories/index.js (350 lines of code) (raw):
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import React, { cloneElement } from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { scaleCategory20 } from 'd3-scale';
import { withState } from 'recompose';
import {
InteractiveForceGraph,
ForceGraph,
ForceGraphNode,
ForceGraphLink,
ForceGraphArrowLink
} from '../src/';
import lesMisJSON from './les-miserables.json';
import './demo-styles.css';
function attachEvents(child) {
return cloneElement(child, {
onMouseDown: action(`clicked <${child.type.name} />`),
onMouseOver: action(`hovered <${child.type.name} />`),
onMouseOut: action(`blurred <${child.type.name} />`),
});
}
const twoChildren = [
<ForceGraphNode node={{ id: 'first-node' }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'second-node' }} fill="#47d3d9" />,
<ForceGraphLink link={{ source: 'first-node', target: 'second-node' }} />,
].map(attachEvents);
const twoChildrenArrow = [
<ForceGraphNode node={{ id: 'first-node' }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'second-node' }} fill="#47d3d9" />,
<ForceGraphArrowLink targetRadius={2} link={{ source: 'first-node', target: 'second-node' }} />,
].map(attachEvents);
const tenChildren = [
<ForceGraphNode node={{ id: 'first-node', radius: 5 }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'second-node', radius: 10 }} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'third-node', radius: 15 }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'fourth-node', radius: 15 }} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'fifth-node', radius: 5 }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'sixth-node', radius: 15 }} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'seventh-node', radius: 10 }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'eighth-node', radius: 5 }} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'ninth-node', radius: 5 }} fill="#11939A" />,
<ForceGraphNode node={{ id: 'tenth-node', radius: 5 }} fill="#47d3d9" />,
<ForceGraphLink link={{ source: 'first-node', target: 'second-node' }} />,
<ForceGraphLink link={{ source: 'third-node', target: 'second-node' }} />,
<ForceGraphLink link={{ source: 'third-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'fifth-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'fifth-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'sixth-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'seventh-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'eighth-node', target: 'fourth-node' }} />,
<ForceGraphLink link={{ source: 'ninth-node', target: 'tenth-node' }} />,
<ForceGraphLink link={{ source: 'tenth-node', target: 'fifth-node' }} />,
].map(attachEvents);
const tenChildrenArrows = [
<ForceGraphNode node={{ id: 'first-node'}} fill="#11939A" />,
<ForceGraphNode node={{ id: 'second-node'}} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'third-node'}} fill="#11939A" />,
<ForceGraphNode node={{ id: 'fourth-node'}} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'fifth-node'}} fill="#11939A" />,
<ForceGraphNode node={{ id: 'sixth-node'}} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'seventh-node'}} fill="#11939A" />,
<ForceGraphNode node={{ id: 'eighth-node'}} fill="#47d3d9" />,
<ForceGraphNode node={{ id: 'ninth-node'}} fill="#11939A" />,
<ForceGraphNode node={{ id: 'tenth-node'}} fill="#47d3d9" />,
<ForceGraphArrowLink link={{ source: 'first-node', target: 'second-node' }} />,
<ForceGraphArrowLink link={{ source: 'third-node', target: 'second-node' }} />,
<ForceGraphArrowLink link={{ source: 'third-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'fifth-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'fifth-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'sixth-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'seventh-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'eighth-node', target: 'fourth-node' }} />,
<ForceGraphArrowLink link={{ source: 'ninth-node', target: 'tenth-node' }} />,
<ForceGraphArrowLink link={{ source: 'tenth-node', target: 'fifth-node' }} />,
].map(attachEvents);
storiesOf('<ForceGraph />', module)
.add('two nodes', () => (
<ForceGraph>{twoChildren}</ForceGraph>
))
.add('10 nodes', () => (
<ForceGraph>{tenChildren}</ForceGraph>
))
.add('10 nodes (animated)', () => (
<ForceGraph simulationOptions={{ animate: true }}>{tenChildren}</ForceGraph>
))
.add('10 nodes (strength on x and y)', () => (
<ForceGraph
simulationOptions={{
strength: {
x: ({ radius }) => 15 / radius,
y: ({ radius }) => 3 / radius,
}
}}
>
{tenChildren}
</ForceGraph>
))
.add('10 nodes (strength on collisions)', () => (
<ForceGraph
simulationOptions={{
animate: true,
strength: {
collide: 8,
}
}}
>
{tenChildren}
</ForceGraph>
))
.add('labelled nodes', () => (
<ForceGraph showLabels>
{twoChildren}
</ForceGraph>
))
.add('arrow links', () => (
<ForceGraph>
{twoChildrenArrow}
</ForceGraph>
))
.add('10 nodes (with arrows)', () => (
<ForceGraph simulationOptions={{
strength: {
collide: 10,
}
}}>
{tenChildrenArrows}
</ForceGraph>
))
.add('Characters from Les Mis', () => {
const scale = scaleCategory20();
return (
<ForceGraph>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</ForceGraph>
);
})
.add('Characters from Les Mis (animated)', () => {
const scale = scaleCategory20();
return (
<ForceGraph simulationOptions={{ animate: true }} xmlnsXlink="http://www.w3.org/1999/xlink">
<a href="https://bl.ocks.org/mbostock/4062045" target="_blank">
<text x={25} y={25} style={{ fontSize: 10 }}>{'https://bl.ocks.org/mbostock/4062045'}</text>
</a>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</ForceGraph>
);
})
.add('Characters from Les Mis (zoomable)', () => {
const scale = scaleCategory20();
return (
<ForceGraph
zoom
zoomOptions={{minScale: 0.25, maxScale: 5, onZoom: action('zoomed'), onPan: action('panned')}}
>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</ForceGraph>
);
});;
storiesOf('<InteractiveForceGraph />', module)
.add('two nodes', () => (
<InteractiveForceGraph onSelectNode={action('node selected')} onDeselectNode={action('node deselected')}>
{twoChildren}
</InteractiveForceGraph>
))
.add('10 nodes', () => (
<InteractiveForceGraph onSelectNode={action('node selected')} onDeselectNode={action('node deselected')}>
{tenChildren}
</InteractiveForceGraph>
))
.add('10 nodes (animated)', () => (
<InteractiveForceGraph onSelectNode={action('node selected')} onDeselectNode={action('node deselected')} simulationOptions={{ animate: true }}>
{tenChildren}
</InteractiveForceGraph>
))
.add('10 nodes (strength on x and y)', () => (
<InteractiveForceGraph
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
simulationOptions={{
strength: {
x: ({ radius }) => 15 / radius,
y: ({ radius }) => 3 / radius,
}
}}
>
{tenChildren}
</InteractiveForceGraph>
))
.add('10 nodes (strength on collisions)', () => (
<InteractiveForceGraph
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
simulationOptions={{
animate: true,
strength: {
collide: 8,
}
}}
>
{tenChildren}
</InteractiveForceGraph>
))
.add('10 nodes (managed selection)', (...args) => {
const ids = [
'first-node',
'second-node',
'third-node',
'fourth-node',
'fifth-node',
'sixth-node',
'seventh-node',
'eighth-node',
'ninth-node',
'tenth-node',
];
const ManagedForceGraph = withState('selection', 'updateSelection', null)(
({ selection, updateSelection }) => (
<div>
<div style={{position: 'absolute', top: '1em', left: '1em'}}>
<button type="button" onClick={() => {
const randomIdx = Math.floor(Math.random() * ids.length);
action('trying to pick', randomIdx);
updateSelection({id: ids[randomIdx]});
}}>
{'Pick random selection'}
</button>
<button type="button" onClick={() => {
action('trying to clear selection');
updateSelection(null);
}}>
{'Clear selection'}
</button>
</div>
<InteractiveForceGraph
selectedNode={selection}
onSelectNode={(ev, newValue) => {
action('node selected');
updateSelection(newValue);
}}
onDeselectNode={(ev, newValue) => {
action('node deselected');
updateSelection(null);
}}
>
{tenChildren}
</InteractiveForceGraph>
</div>
)
);
return <ManagedForceGraph />;
})
.add('labelled nodes', () => (
<InteractiveForceGraph
showLabels
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
>
{twoChildren}
</InteractiveForceGraph>
))
.add('Characters from Les Mis', () => {
const scale = scaleCategory20();
return (
<InteractiveForceGraph
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</InteractiveForceGraph>
);
})
.add('Characters from Les Mis (highlighted dependencies)', () => {
const scale = scaleCategory20();
return (
<InteractiveForceGraph
highlightDependencies
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</InteractiveForceGraph>
);
})
.add('Characters from Les Mis (animated)', () => {
const scale = scaleCategory20();
return (
<InteractiveForceGraph
highlightDependencies
simulationOptions={{ animate: true }}
onSelectNode={action('node selected')}
onDeselectNode={action('node deselected')}
>
{lesMisJSON.nodes.map(node => (
<ForceGraphNode
key={node.id}
fill={scale(node.group)}
node={{ ...node, radius: 5 }}
/>
)).map(attachEvents)}
{lesMisJSON.links.map(link => (
<ForceGraphLink
key={`${link.source}=>${link.target}`}
link={{ ...link, value: 2 }}
/>
)).map(attachEvents)}
</InteractiveForceGraph>
);
});