Hello,
I have been using React GoJS for a project and need to use the Radial Layout. I mostly followed and modified the gojs-react-basic example in GitHub. I followed the Radial Script for examples. I have copied the RadialLayout.ts into the components folder.
However, when I ran the project, my diagram does not appear and it is completely empty as shown in the screenshot below. All other layouts work so I am not sure why this does not.
I am not sure why this happens but I suspect it is because I have no root nodes? I have tried to put nodeClicked function into the componentDidMount() with the first nodeDataArray prop item as the node but it does not appear to work. Here is the code I put inside componentDidMount(): nodeClicked(null, this.props.nodeDataArray[0])
I am unable to figure out how to solve these two problems:
- How to make Radial Layout appear in my diagram?
- How do I make sure that there is already a root node upon loading?
Thank you!
DiagramWrapper.tsx for reference:
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import * as React from 'react';
import { RadialLayout } from './RadialLayout';
import './Diagram.css';
class CustomRadialLayout extends RadialLayout {
rotateNode(node: go.Node, angle: number, sweep: number, radius: number) {
// rotate the nodes and make sure the text is not upside-down
node.angle = angle;
const label = node.findObject('TEXTBLOCK');
if (label !== null) {
label.angle = ((angle > 90 && angle < 270 || angle < -90) ? 180 : 0);
}
}
commitLayers() {
// optional: add circles in the background
// need to remove any old ones first
const diagram = this.diagram;
if (diagram === null) return;
const gridlayer = diagram.findLayer('Grid');
if (gridlayer === null) return;
const root = this.root;
if (root === null) return;
const circles = new go.Set<go.Part>();
gridlayer.parts.each(function(circle) {
if (circle.name === 'CIRCLE') circles.add(circle);
});
circles.each(function(circle) {
diagram.remove(circle);
});
// add circles centered at the root
const $$ = go.GraphObject.make; // for conciseness in defining templates
for (let lay = 1; lay <= this.maxLayers; lay++) {
const radius = lay * this.layerThickness;
const circle =
$$(go.Part,
{ name: 'CIRCLE', layerName: 'Grid' },
{ locationSpot: go.Spot.Center, location: root.location },
$$(go.Shape, 'Circle',
{ width: radius * 2, height: radius * 2 },
{ fill: 'rgba(200,200,200,0.2)', stroke: null }));
diagram.add(circle);
}
}
}
interface DiagramProps {
nodeDataArray: Array<go.ObjectData>;
linkDataArray: Array<go.ObjectData>;
modelData: go.ObjectData;
skipsDiagramUpdate: boolean;
onDiagramEvent: (e: go.DiagramEvent) => void;
onModelChange: (e: go.IncrementalData) => void;
}
function nodeClicked(e: go.InputEvent | null, root: go.GraphObject| null) {
if (!(root instanceof go.Node)) return;
const diagram = root.diagram;
if (diagram === null) return;
// all other nodes should be visible and use the default category
diagram.nodes.each(function(n) {
n.visible = true;
if (n !== root) n.category = '';
});
// make this Node the root
root.category = 'Root';
// tell the RadialLayout what the root node should be
(diagram.layout as RadialLayout).root = root;
console.log(root);
diagram.layoutDiagram(true);
}
export class DiagramWrapper extends React.Component<DiagramProps, {}> {
/**
* Ref to keep a reference to the Diagram component, which provides access to the GoJS diagram via getDiagram().
*/
private diagramRef: React.RefObject<ReactDiagram>;
/** @internal */
constructor(props: DiagramProps) {
super(props);
this.diagramRef = React.createRef();
}
/**
* Get the diagram reference and add any desired diagram listeners.
* Typically the same function will be used for each listener, with the function using a switch statement to handle the events.
*/
public componentDidMount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.addDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Get the diagram reference and remove listeners that were added during mounting.
*/
public componentWillUnmount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.removeDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Diagram initialization method, which is passed to the ReactDiagram component.
* This method is responsible for making the diagram and initializing the model, any templates,
* and maybe doing other initialization tasks like customizing tools.
* The model's data should not be set here, as the ReactDiagram component handles that.
*/
private initDiagram(): go.Diagram {
const $ = go.GraphObject.make;
// set your license key here before creating the diagram: go.Diagram.licenseKey = "...";
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true, // must be set to allow for model change listening
initialAutoScale: go.Diagram.Uniform,
padding: 10,
isReadOnly: true,
layout: $(CustomRadialLayout, { maxLayers: 1 }),
'animationManager.isEnabled': false,
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key', // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
// positive keys for nodes
makeUniqueKeyFunction: (m: go.Model, data: any) => {
let k = data.key || 1;
while (m.findNodeDataForKey(k)) k++;
data.key = k;
return k;
},
// negative keys for links
makeUniqueLinkKeyFunction: (m: go.GraphLinksModel, data: any) => {
let k = data.key || -1;
while (m.findLinkDataForKey(k)) k--;
data.key = k;
return k;
}
})
});
// shows when hovering over a node
const commonToolTip =
$<go.Adornment>('ToolTip',
$(go.Panel, 'Vertical',
{ margin: 3 },
$(go.TextBlock, // bound to node data
{ margin: 4, font: 'bold 12pt sans-serif' },
new go.Binding('text')),
$(go.TextBlock, // bound to node data
new go.Binding('text', 'color', function(c) { return 'Color: ' + c; })),
$(go.TextBlock, // bound to Adornment because of call to Binding.ofObject
new go.Binding('text', '', function(ad) { return 'Connections: ' + ad.adornedPart.linksConnected.count; }).ofObject())
) // end Vertical Panel
); // end Adornment
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, 'Spot',
{
locationSpot: go.Spot.Center,
locationObjectName: 'SHAPE', // Node.location is the center of the Shape
selectionAdorned: false,
doubleClick: nodeClicked,
toolTip: commonToolTip
}, // the Shape will go around the TextBlock
$(go.Shape, 'Circle',
{
name: 'SHAPE',
fill: 'lightgray', // default value, but also data-bound
stroke: 'transparent',
strokeWidth: 2,
desiredSize: new go.Size(20, 20),
portId: '' // so links will go to the shape, not the whole node
},
new go.Binding('fill', 'color')),
$(go.TextBlock,
{
name: 'TEXTBLOCK',
alignment: go.Spot.Right,
alignmentFocus: go.Spot.Left
},
new go.Binding('text').makeTwoWay())
);
// this is the root node, at the center of the circular layers
diagram.nodeTemplateMap.add('Root',
$(go.Node, 'Auto',
{
locationSpot: go.Spot.Center,
selectionAdorned: false,
toolTip: commonToolTip
},
$(go.Shape, 'Circle',
{ fill: 'white' }),
$(go.TextBlock,
{ font: 'bold 12pt sans-serif', margin: 5 },
new go.Binding('text'))
));
// relinking depends on modelData
diagram.linkTemplate =
$(go.Link,
{
routing: go.Link.Normal,
curve: go.Link.Bezier,
selectionAdorned: false,
layerName: 'Background'
},
$(go.Shape,
{
stroke: 'black', // default value, but is data-bound
strokeWidth: 1
},
new go.Binding('stroke', 'color'))
);
return diagram;
}
public render() {
return (
<>
<ReactDiagram
ref={this.diagramRef}
divClassName='diagram-component'
initDiagram={this.initDiagram}
nodeDataArray={this.props.nodeDataArray}
linkDataArray={this.props.linkDataArray}
modelData={this.props.modelData}
onModelChange={this.props.onModelChange}
skipsDiagramUpdate={this.props.skipsDiagramUpdate}
/>
</>
);
}
}