Skip to content

Commit ec2b703

Browse files
Merge pull request #43 from commitd/stuarthendren/41
fix(layout): fix to allow custom layout to run in built versions
2 parents a1ca7ba + ab1ee30 commit ec2b703

File tree

5 files changed

+115
-51
lines changed

5 files changed

+115
-51
lines changed

example/index.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,67 @@ import {
99
ThemeProvider,
1010
ThemeSwitch,
1111
} from '@committed/components'
12+
import { use } from 'cytoscape'
1213
import * as React from 'react'
1314
import * as ReactDOM from 'react-dom'
1415
import {
1516
addRandomEdge,
1617
addRandomNode,
18+
CustomGraphLayout,
1719
cytoscapeRenderer,
20+
DecoratedNode,
1821
Graph,
1922
GraphModel,
2023
GraphToolbar,
2124
initializeCytoscape,
2225
ModelNode,
2326
NodeViewer,
2427
} from '../dist/committed-components-graph.cjs.js'
25-
import { use } from 'cytoscape'
2628

2729
initializeCytoscape(use)
2830

31+
const sortedLayout: CustomGraphLayout = {
32+
name: 'Data Structure',
33+
runLayout: (model: GraphModel): Record<string, cytoscape.Position> => {
34+
const paddingTop = 50
35+
const paddingLeft = 50
36+
const columnWidth = 250
37+
const rowHeight = 75
38+
const byLabel: Record<string, DecoratedNode[]> = model.nodes.reduce<
39+
Record<string, DecoratedNode[]>
40+
>((acc, next) => {
41+
acc[(next.label ?? 'unknown') as string] = (
42+
acc[(next.label ?? 'unknown') as string] ?? []
43+
).concat(next)
44+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
45+
return acc
46+
}, {})
47+
const sortedByLabel: DecoratedNode[][] = Object.keys(byLabel)
48+
.sort((a, b) => a.localeCompare(b))
49+
.map((label): DecoratedNode[] => byLabel[label])
50+
51+
let column = 0
52+
return sortedByLabel.reduce<Record<string, cytoscape.Position>>(
53+
(acc, nodes) => {
54+
let row = 0
55+
nodes.forEach((n) => {
56+
acc[n.id] = {
57+
x: column * columnWidth + paddingLeft,
58+
y: row * rowHeight + paddingTop,
59+
}
60+
row++
61+
})
62+
63+
column++
64+
return acc
65+
},
66+
{}
67+
)
68+
},
69+
// eslint-disable-next-line @typescript-eslint/no-empty-function
70+
stopLayout: () => {},
71+
}
72+
2973
const App: React.FC = () => {
3074
const [model, setModel] = React.useState(
3175
addRandomEdge(addRandomNode(GraphModel.createEmpty(), 20), 15)
@@ -42,7 +86,7 @@ const App: React.FC = () => {
4286
model={model}
4387
onModelChange={setModel}
4488
iconStyle={{ color: '$brandContrast' }}
45-
layouts={cytoscapeRenderer.layouts}
89+
layouts={[...cytoscapeRenderer.layouts, sortedLayout]}
4690
/>
4791
<ThemeSwitch />
4892
</AppBarActions>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
},
2222
"scripts": {
2323
"clean": "shx rm -rf dist",
24+
"commit": "cz",
2425
"build": "rollpkg build --tsconfig ./tsconfig.build.json",
2526
"watch": "rollpkg watch",
2627
"lint": "eslint './src/**/*.{ts,tsx}'",

src/graph/renderer/CytoscapeGraphLayoutAdapter.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import { CollectionArgument, NodeCollection } from 'cytoscape'
12
import { GraphModel } from '../GraphModel'
23
import {
3-
CustomLayoutOptions,
4+
CUSTOM_LAYOUT_NAME,
45
CytoscapeGraphLayoutAdapter,
6+
CytoscapeGraphLayoutAdapterManipulation,
7+
CytoscapeGraphLayoutAdapterOptions,
8+
register,
59
} from './CytoscapeGraphLayoutAdapter'
6-
import { CollectionArgument, NodeCollection } from 'cytoscape'
710

811
it('should not throw if cytoscape undefined', () => {
9-
expect(CytoscapeGraphLayoutAdapter.register).not.toThrow()
12+
const cyUseMock = jest.fn()
13+
expect(() => register(cyUseMock)).not.toThrow()
1014
})
1115

12-
it('should throw if no algorigthm defined', () => {
13-
const adapter = new CytoscapeGraphLayoutAdapter(
14-
{} as cytoscape.LayoutPositionOptions & CustomLayoutOptions
16+
it('should throw if no algorithm defined', () => {
17+
const adapter = {} as CytoscapeGraphLayoutAdapterManipulation
18+
CytoscapeGraphLayoutAdapter.bind(adapter)(
19+
{} as CytoscapeGraphLayoutAdapterOptions
1520
)
1621
expect(() => {
1722
adapter.run()
@@ -21,12 +26,12 @@ it('should throw if no algorigthm defined', () => {
2126
it('should register with cytoscape', () => {
2227
const cyMock = jest.fn()
2328

24-
CytoscapeGraphLayoutAdapter.register(cyMock)
29+
register(cyMock)
2530

2631
expect(cyMock).toHaveBeenCalled()
2732
expect(cyMock).toHaveBeenCalledWith(
2833
'layout',
29-
CytoscapeGraphLayoutAdapter.LAYOUT_NAME,
34+
CUSTOM_LAYOUT_NAME,
3035
CytoscapeGraphLayoutAdapter
3136
)
3237
})
@@ -49,7 +54,8 @@ it('should run layout', () => {
4954
positions,
5055
} as unknown as NodeCollection),
5156
} as CollectionArgument
52-
const adapter = new CytoscapeGraphLayoutAdapter({
57+
const adapter = {} as CytoscapeGraphLayoutAdapterManipulation
58+
CytoscapeGraphLayoutAdapter.bind(adapter)({
5359
model,
5460
algorithm,
5561
eles,
Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,74 @@
1-
import { Ext, LayoutManipulation, LayoutPositionOptions, Core } from 'cytoscape'
1+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
2+
/* eslint-disable @typescript-eslint/ban-ts-comment */
3+
import {
4+
Core,
5+
Ext,
6+
LayoutEventObject,
7+
LayoutManipulation,
8+
LayoutPositionOptions,
9+
} from 'cytoscape'
210
import { GraphModel } from '../GraphModel'
311
import { CustomGraphLayout } from '../types'
412

5-
export type CustomLayoutOptions = {
13+
export type CytoscapeGraphLayoutAdapterOptions = LayoutPositionOptions & {
14+
name: string
615
model: GraphModel
716
algorithm: CustomGraphLayout
17+
// Provided
18+
cy: Core
819
}
920

10-
export class CytoscapeGraphLayoutAdapter implements LayoutManipulation {
11-
private readonly options: LayoutPositionOptions & CustomLayoutOptions
12-
13-
static readonly LAYOUT_NAME: string = 'custom'
21+
export type CytoscapeGraphLayoutAdapterManipulation = LayoutManipulation & {
22+
options: CytoscapeGraphLayoutAdapterOptions
23+
}
1424

15-
constructor(options: LayoutPositionOptions & CustomLayoutOptions) {
16-
this.options = options
17-
}
25+
export const CUSTOM_LAYOUT_NAME = 'custom'
1826

19-
static register: Ext = (cytoscape) => {
20-
if (cytoscape == null) {
21-
return
22-
} // can't register if cytoscape unspecified
23-
cytoscape(
24-
'layout',
25-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
26-
// @ts-ignore
27-
CytoscapeGraphLayoutAdapter.LAYOUT_NAME,
28-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
29-
// @ts-ignore
30-
CytoscapeGraphLayoutAdapter
31-
)
32-
}
27+
export function CytoscapeGraphLayoutAdapter(
28+
this: CytoscapeGraphLayoutAdapterManipulation,
29+
options: CytoscapeGraphLayoutAdapterOptions
30+
): void {
31+
this.options = Object.assign({}, options, { name: CUSTOM_LAYOUT_NAME })
3332

34-
run(): this {
33+
this.run = function (
34+
this: CytoscapeGraphLayoutAdapterManipulation & LayoutEventObject
35+
) {
3536
if (this.options.algorithm == null) {
3637
throw new Error('No custom layout algorithm was specified')
3738
}
38-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
39-
//@ts-ignore
40-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39+
4140
const cy: Core = this.options.cy
4241
const boundingBox = { x1: 0, y1: 0, w: cy.width(), h: cy.height() }
4342
const nodePositions = this.options.algorithm.runLayout(this.options.model, {
4443
boundingBox,
4544
})
46-
this.options.eles.nodes().positions((n) => {
45+
this.options.eles.nodes().positions((n: { id: () => string | number }) => {
4746
return nodePositions[n.id()]
4847
})
4948
return this
5049
}
51-
52-
// alias for run()
53-
start(): this {
50+
this.start = function () {
5451
return this.run()
5552
}
5653

5754
// called on continuous layouts to stop them before they finish
58-
stop(): this {
55+
this.stop = function () {
5956
this.options.algorithm.stopLayout()
6057
return this
6158
}
6259
}
60+
61+
export const register: Ext = (cytoscape) => {
62+
if (cytoscape == null) {
63+
return
64+
} // can't register if cytoscape unspecified
65+
cytoscape(
66+
'layout',
67+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
68+
// @ts-ignore
69+
CUSTOM_LAYOUT_NAME,
70+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
71+
// @ts-ignore
72+
CytoscapeGraphLayoutAdapter
73+
)
74+
}

src/graph/renderer/CytoscapeRenderer.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ import {
4343
PresetGraphLayout,
4444
} from '../types'
4545
import {
46-
CustomLayoutOptions,
47-
CytoscapeGraphLayoutAdapter,
46+
CUSTOM_LAYOUT_NAME,
47+
CytoscapeGraphLayoutAdapterOptions,
48+
register,
4849
} from './CytoscapeGraphLayoutAdapter'
4950
import { useCyListener } from './useCyListener'
5051

@@ -59,7 +60,7 @@ export function initializeCytoscape(
5960
cyuse: (module: cytoscape.Ext) => void
6061
): void {
6162
try {
62-
cyuse(CytoscapeGraphLayoutAdapter.register)
63+
cyuse(register)
6364
cyuse(ccola)
6465
} catch {
6566
// Ignore multiple attempts to initialize
@@ -156,10 +157,10 @@ const Renderer: GraphRenderer<CyGraphRendererOptions>['render'] = ({
156157
grid,
157158
cola,
158159
custom: {
159-
name: CytoscapeGraphLayoutAdapter.LAYOUT_NAME,
160+
name: CUSTOM_LAYOUT_NAME,
160161
model: graphModel,
161162
algorithm: graphModel.getCurrentLayout().getLayout(),
162-
} as CustomLayoutOptions & LayoutOptions,
163+
} as CytoscapeGraphLayoutAdapterOptions & LayoutOptions,
163164
}
164165
const nodes = graphModel.nodes
165166
const edges = graphModel.edges
@@ -180,14 +181,14 @@ const Renderer: GraphRenderer<CyGraphRendererOptions>['render'] = ({
180181
typeof graphLayout === 'string'
181182
? layouts[graphLayout]
182183
: ({
183-
name: CytoscapeGraphLayoutAdapter.LAYOUT_NAME,
184+
name: CUSTOM_LAYOUT_NAME,
184185
model: graphModel,
185186
algorithm: graphLayout,
186-
} as CustomLayoutOptions)
187+
} as CytoscapeGraphLayoutAdapterOptions)
187188
if (l == null) {
188189
throw new Error('Layout does not exist')
189190
}
190-
cytoscape.layout(l as LayoutOptions).run()
191+
cytoscape.layout(l).run()
191192
}
192193
},
193194
200

0 commit comments

Comments
 (0)