Giter VIP home page Giter VIP logo

react-cytoscapejs's Introduction

react-cytoscapejs

The react-cytoscapejs package is an MIT-licensed React component for network (or graph, as in graph theory) visualisation. The component renders a Cytoscape graph.

Most props of this component are Cytoscape JSON.

Usage

npm

npm install react-cytoscapejs
npm install [email protected] # your desired version, 3.2.19 or newer

yarn

yarn add react-cytoscapejs
yarn add [email protected] # your desired version, 3.2.19 or newer

Note that you must specify the desired version of cytoscape to be used. Otherwise, you will get whatever version npm or yarn thinks best matches this package's compatible semver range -- which is currently ^3.2.19 or any version of 3 newer than or equal to 3.2.19.

The component is created by putting a <CytoscapeComponent> within the render() function of one of your apps's React components. Here is a minimal example:

import React from 'react';
import ReactDOM from 'react-dom';
import CytoscapeComponent from 'react-cytoscapejs';

class MyApp extends React.Component {
  constructor(props){
    super(props);
  }

  render(){
    const elements = [
       { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
       { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
       { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
    ];

    return <CytoscapeComponent elements={elements} style={ { width: '600px', height: '600px' } } />;
  }
}

ReactDOM.render( React.createElement(MyApp, document.getElementById('root')));

Basic props

elements

The flat list of Cytoscape elements to be included in the graph, each represented as non-stringified JSON. E.g.:

<CytoscapeComponent
  elements={[
    { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
    { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
    {
      data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' }
    }
  ]}
/>

Note that arrays or objects should not be used in an element's data or scratch fields, unless using a custom diff() prop.

In order to make it easier to support passing in elements JSON in the elements: { nodes: [], edges: [] } format, there is a static function CytoscapeComponent.normalizeElements(). E.g.:

<CytoscapeComponent
  elements={CytoscapeComponent.normalizeElements({
    nodes: [
      { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
      { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } }
    ],
    edges: [
      {
        data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' }
      }
    ]
  })}
/>

Note that CytoscapeComponent.normalizeElements() is useful only for plain-JSON data, such as an export from Cytoscape.js or the Cytoscape desktop software. If you use custom prop types, such as Immutable, then you should flatten the elements yourself before passing the elements prop.

stylesheet

The Cytoscape stylesheet as non-stringified JSON. Note that the prop key is stylesheet rather than style, the key used by Cytoscape itself, so as to not conflict with the HTML style attribute. E.g.:

<CytoscapeComponent
  stylesheet={[
    {
      selector: 'node',
      style: {
        width: 20,
        height: 20,
        shape: 'rectangle'
      }
    },
    {
      selector: 'edge',
      style: {
        width: 15
      }
    }
  ]}
/>

layout

Use a layout to automatically position the nodes in the graph. E.g.:

layout: {
  name: 'random';
}

To use an external layout extension, you must register the extension prior to rendering this component, e.g.:

import Cytoscape from 'cytoscape';
import COSEBilkent from 'cytoscape-cose-bilkent';
import React from 'react';
import CytoscapeComponent from 'react-cytoscapejs';

Cytoscape.use(COSEBilkent);

class MyApp extends React.Component {
  render() {
    const elements = [
      { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
      { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
      { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
    ];

    const layout = { name: 'cose-bilkent' };

    return <CytoscapeComponent elements={elements} layout={layout} />;
  }
}

cy

This prop allows for getting a reference to the Cytoscape cy reference using a React ref function. This cy reference can be used to access the Cytoscape API directly. E.g.:

class MyApp extends React.Component {
  render() {
    return <CytoscapeComponent cy={(cy) => { this.cy = cy }}>;
  }
}

Viewport manipulation

pan

The panning position of the graph, e.g. <CytoscapeComponent pan={ { x: 100, y: 200 } } />.

zoom

The zoom level of the graph, e.g. <CytoscapeComponent zoom={2} />.

Viewport mutability & gesture toggling

panningEnabled

Whether the panning position of the graph is mutable overall, e.g. <CytoscapeComponent panningEnabled={false} />.

userPanningEnabled

Whether the panning position of the graph is mutable by user gestures such as swiping, e.g. <CytoscapeComponent userPanningEnabled={false} />.

minZoom

The minimum zoom level of the graph, e.g. <CytoscapeComponent minZoom={0.5} />.

maxZoom

The maximum zoom level of the graph, e.g. <CytoscapeComponent maxZoom={2} />.

zoomingEnabled

Whether the zoom level of the graph is mutable overall, e.g. <CytoscapeComponent zoomingEnabled={false} />.

userZoomingEnabled

Whether the zoom level of the graph is mutable by user gestures (e.g. pinch-to-zoom), e.g. <CytoscapeComponent userZoomingEnabled={false} />.

boxSelectionEnabled

Whether shift+click-and-drag box selection is enabled, e.g. <CytoscapeComponent boxSelectionEnabled={false} />.

autoungrabify

If true, nodes automatically can not be grabbed regardless of whether each node is marked as grabbable, e.g. <CytoscapeComponent autoungrabify={true} />.

autolock

If true, nodes can not be moved at all, e.g. <CytoscapeComponent autolock={true} />.

autounselectify

If true, elements have immutable selection state, e.g. <CytoscapeComponent autounselectify={true} />.

HTML attribute props

These props allow for setting built-in HTML attributes on the div created by the component that holds the visualisation:

id

The id attribute of the div, e.g. <CytoscapeComponent id="myCy" />.

className

The class attribute of the div containing space-separated class names, e.g. <CytoscapeComponent className="foo bar" />.

style

The style attribute of the div containing CSS styles, e.g. <CytoscapeComponent style={ { width: '600px', height: '600px' } } />.

Custom prop types

This component allows for props of custom type to be used (i.e. non JSON props), for example an object-oriented model or an Immutable model. The props used to control the reading and diffing of the main props are listed below.

Examples are given using Immutable. Using Immutable allows for cheaper diffs, which is useful for updating graphs with many elements. For example, you may specify elements as the following:

const elements = Immutable.List([
  Immutable.Map({ data: Immutable.Map({ id: 'foo', label: 'bar' }) })
]);

get(object, key)

Get the value of the specified object at the key, which may be an integer in the case of lists/arrays or strings in the case of maps/objects. E.g.:

const get = (object, key) => {
  // must check type because some props may be immutable and others may not be
  if (Immutable.Map.isMap(object) || Immutable.List.isList(object)) {
    return object.get(key);
  } else {
    return object[key];
  }
}

The default is:

const get = (object, key) => object[key];

toJson(object)

Get the deep value of the specified object as non-stringified JSON. E.g.:

const toJson = (object) => {
  // must check type because some props may be immutable and others may not be
  if (Immutable.isImmutable(object)) {
    return object.toJSON();
  } else {
    return object;
  }
}

The default is:

const toJson = (object) => object;

diff(objectA, objectB)

Return whether the two objects have equal value. This is used to determine if and where Cytoscape needs to be patched. E.g.:

const diff = (objectA, objectB) => objectA !== objectB; // immutable creates new objects for each operation

The default is a shallow equality check over the fields of each object. This means that if you use the default diff(), you should not use arrays or objects in an element's data or scratch fields.

Immutable benefits performance here by reducing the total number of diff() calls needed. For example, an unchanged element requires only one diff with Immutable whereas it would require many diffs with the default JSON diff() implementation. Basically, Immutable make diffs minimal-depth searches.

forEach(list, iterator)

Call iterator on each element in the list, in order. E.g.:

const forEach = (list, iterator) => list.forEach(iterator); // same for immutable and js arrays

The above example is the same as the default forEach().

Reference props

cy()

The cy prop allows for getting a reference to the cy Cytoscape object, e.g.:

<CytoscapeComponent cy={(cy) => { myCyRef = cy }} />

Change log

  • v2.0.0
    • BREAKING: Move cytoscape to peer dependencies for easier use in other packages. In particular, since you frequently need to explicitly call cytoscape functionality in your larger project, this helps ensure only one copy of it is loaded.
    • Change from webpack to microbundle (rollup based)
    • Update dependencies and lint configurations
    • With thanks to @akx for the contribution
  • v1.2.1
    • When patching, apply layout outside of batching.
  • v1.2.0
    • Add support for headless, styleEnabled and the following (canvas renderer) rendering hints: hideEdgesOnViewport, textureOnViewport, motionBlur, motionBlurOpacity, wheelSensitivity, pixelRatio
    • Add setup and version explanation to README
    • Add a default React displayName
  • v1.1.0
    • Add Component.normalizeElements() utility function
    • Update style prop docs
  • v1.0.1
    • Update style attribute in docs example to use idiomatic React style object
    • Add npmignore
  • v1.0.0
    • Initial release

react-cytoscapejs's People

Contributors

akx avatar alexcjohnson avatar azemetre avatar bpostlethwaite avatar cg-n avatar kinimesi avatar maxkfranz avatar nicolaskruchten avatar ocanty avatar pawel-schmidt avatar sgratzl avatar spaxe avatar udayan28 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-cytoscapejs's Issues

When a component is re-rendered or state is mutated cytoscape nodes array is not cleared

When my component re-renders or i mutate the state with a setter, the array containing the nodes is not cleared so we end up with a progressively larger array containing the copies of the same data.

What I also do not understand is if the array is getting larger with additional nodes on each rerender why is cytoscape not renderering more and more nodes ?

Stumped on this. Is it a bug or am I and idiot ?

Screen Shot 2020-12-03 at 20 58 25

Screen Shot 2020-12-03 at 20 59 37

Elements do not update when they are stored as a state

Description

When I store the current elements in the graph as a state (this.state.elements), and at render time elements is passed to Cytoscape elements prop, it is not updated correctly inside the graph. Here's a snippet:

  render() {
    const {
      selectedLayout,
      style,
      elements,
      layout
     } = this.state;

    return (
      <div>
        <CytoscapeComponent
            elements={elements}
            style={style}
            cy={this.handleCy}
            layout={{name: layout}}
        />
       ...
      </div>
    );
  }

Example

In my example, whenever the button is clicked, a new node is created and added to the elements array (stored as a state). I log the size of the node inside the console and as seen, the state is correctly changing. Here's a picture:
capture

Reproducing

Please refer to the previous issues.

Code

Save this code as App.js:

import React, { Component } from 'react';
import CytoscapeComponent from './react-cytoscapejs/src/component.js';
import Select from 'react-select';
import _ from 'lodash';

const layoutOptions = [
  { value: 'preset', label: 'preset' },
  { value: 'random', label: 'random' },
  { value: 'grid', label: 'grid' },
  { value: 'circle', label: 'circle' }
];

const original_elements = [
    {
        data: {id: 'one', label: 'Node 1'},
        position: {x: 50, y: 50}
    },
    {
        data: {id: 'two', label: 'Node 2'},
        position: {x: 200, y: 200}
    },
    {
        data: {id: 'three', label: 'Node 3'},
        position: {x: 100, y: 150}
    },
    {
        data: {id: 'four', label: 'Node 4'},
        position: {x: 400, y: 50}
    },
    {
        data: {id: 'five', label: 'Node 5'},
        position: {x: 250, y: 100}
    },
    {
        data: {id: 'six', label: 'Node 6', parent: 'three'},
        position: {x: 150, y: 150}
    },

    {data: {
        source: 'one',
        target: 'two',
        label: 'Edge from Node1 to Node2'
    }},
    {data: {
        source: 'one',
        target: 'five',
        label: 'Edge from Node 1 to Node 5'
    }},
    {data: {
        source: 'two',
        target: 'four',
        label: 'Edge from Node 2 to Node 4'
    }},
    {data: {
        source: 'three',
        target: 'five',
        label: 'Edge from Node 3 to Node 5'
    }},
    {data: {
        source: 'three',
        target: 'two',
        label: 'Edge from Node 3 to Node 2'
    }},
    {data: {
        source: 'four',
        target: 'four',
        label: 'Edge from Node 4 to Node 4'
    }},
    {data: {
        source: 'four',
        target: 'six',
        label: 'Edge from Node 4 to Node 6'
    }},
    {data: {
        source: 'five',
        target: 'one',
        label: 'Edge from Node 5 to Node 1'
    }},
];


class App extends Component {
  state = {
    selectedLayout: 'random',
    sourceDistance: 0,
    targetDistance: 0,
    elements: original_elements,
    layout: 'preset',
    boxNodeData: [],
    style: {width:'100%', height: '65vh'}
  }
  _handleCyCalled = false;

  updateLayout = newOption => {
    this.setState({
      selectedLayout: newOption,
      layout: newOption.value
    });
    console.log(`Layout selected:`, newOption);
  }

  addNode = () => {
    const {
      elements
    } = this.state;

    const newId = elements.length.toString();
    const newLabel = "Node " + newId;

    // console.log(newLabel, newId);
    console.log(elements);

    elements.push({data: {id: newId, label: newLabel}});
    elements.push({data: {
      source: 'one',
      target: newId,
    }});

    this.setState({
      elements: elements
    })
  }

  generateNode = event => {
    const ele = event.target;

    const isParent = ele.isParent();
    const isChildless = ele.isChildless();
    const isChild = ele.isChild();
    const isOrphan = ele.isOrphan();
    const renderedPosition = ele.renderedPosition();
    const relativePosition = ele.relativePosition();
    const parent = ele.parent();
    const style = ele.style();
    // Trim down the element objects to only the data contained
    const edgesData = ele.connectedEdges().map(ele => {return ele.data()});
    const childrenData = ele.children().map(ele => {return ele.data()});
    const ancestorsData = ele.ancestors().map(ele => {return ele.data()});
    const descendantsData = ele.descendants().map(ele => {return ele.data()});
    const siblingsData = ele.siblings().map(ele => {return ele.data()});

    const {timeStamp} = event;
    const {
      classes,
      data,
      grabbable,
      group,
      locked,
      position,
      selected,
      selectable
    } = ele.json();

    let parentData;
    if (parent) {
      parentData = parent.data();
    } else {
      parentData = null;
    }

    const nodeObject = {
      // Nodes attributes
      edgesData,
      renderedPosition,
      timeStamp,
      // From ele.json()
      classes,
      data,
      grabbable,
      group,
      locked,
      position,
      selectable,
      selected,
      // Compound Nodes additional attributes
      ancestorsData,
      childrenData,
      descendantsData,
      parentData,
      siblingsData,
      isParent,
      isChildless,
      isChild,
      isOrphan,
      relativePosition,
      // Styling
      style
    };
    return nodeObject;
  }

  generateEdge = event => {
    const ele = event.target;

    const midpoint = ele.midpoint();
    const isLoop = ele.isLoop();
    const isSimple = ele.isSimple();
    const sourceData = ele.source().data();
    const sourceEndpoint = ele.sourceEndpoint();
    const style = ele.style();
    const targetData = ele.target().data();
    const targetEndpoint = ele.targetEndpoint();

    const {timeStamp} = event;
    const {
      classes,
      data,
      grabbable,
      group,
      locked,
      selectable,
      selected,
    } = ele.json();

    const edgeObject = {
      // Edges attributes
      isLoop,
      isSimple,
      midpoint,
      sourceData,
      sourceEndpoint,
      targetData,
      targetEndpoint,
      timeStamp,
      // From ele.json()
      classes,
      data,
      grabbable,
      group,
      locked,
      selectable,
      selected,
      // Styling
      style
    };

    return edgeObject;
  }

  handleCy = cy => {
    if (cy === this._cy && this._handleCyCalled) {
      return;
    }
    this._cy = cy;
    window.cy = cy;
    this._handleCyCalled = true;

    cy.on('tap', 'node', event => {
      const nodeObject = this.generateNode(event);
      console.log('nodeObject:', nodeObject);

      this.setState({
        nodeObject
      })
    })

    cy.on('tap', 'edge', event => {
      const edgeObject = this.generateEdge(event);
      console.log('edgeObject:', edgeObject);

      this.setState({
        edgesData: edgeObject
      })
    })

    cy.on('mouseover', 'node', event => {
      const nodeData = event.target._private.data;
      // console.log(event.target.data());

      this.setState({
        mouseoverNodeData: nodeData
      })
    })

    cy.on('mouseover', 'edge', event => {
      const edgeData = event.target._private.data;
      // console.log(edgeData);

      this.setState({
        mouseoverEdgeData: edgeData
      })
    })

    // SELECTED DATA
    // time delta that separates one select gesture from another
    const SELECT_THRESHOLD = 100;

    const selectedNodes = cy.collection();
    const selectedEdges = cy.collection();

    // We send the selected nodes only if sendSelectedNodesData wasn't called
    // in the previous 100 ms (threshold), which prevents repetitive updates
    const sendSelectedNodesData = _.debounce(() => {
      const elsData = selectedNodes.map(el => el.data());

      // send the array of data json objs somewhere; just log for now...
      console.log('Selected Nodes:', elsData);
    }, SELECT_THRESHOLD);

    const sendSelectedEdgesData = _.debounce(() => {
      const elsData = selectedEdges.map(el => el.data());

      console.log('Selected Edges:', elsData);
    }, SELECT_THRESHOLD);

    cy.on('select', 'node', event => {
      const ele = event.target;

      selectedNodes.merge(ele);
      sendSelectedNodesData();
    });

    cy.on('unselect', 'node', event => {
      const ele = event.target;

      selectedNodes.unmerge(ele);
      sendSelectedNodesData();
    });

    cy.on('select', 'edge', event => {
      const ele = event.target;

      selectedEdges.merge(ele);
      sendSelectedEdgesData();
    });

    cy.on('unselect', 'edge', event => {
      const ele = event.target;

      selectedEdges.unmerge(ele);
      sendSelectedEdgesData();
    });

  }

  render() {
    const {
      selectedLayout,
      style,
      elements,
      layout
     } = this.state;

    return (
      <div>
        <CytoscapeComponent
            elements={elements}
            style={style}
            cy={this.handleCy}
            layout={{name: layout}}
        />
          <p> Layout: </p>
          <Select
            value={selectedLayout}
            onChange={this.updateLayout}
            options={layoutOptions}
          />
          <p><button onClick={this.addNode}>Add a Node</button></p>

      </div>
    );
  }
}

export default App;

Device Event that is fired after elements are selected and box selection has ended

Description

In the cytoscape.js documentation, a list of user input device events is given. Particularly, those are the events relating to box selection:

  • boxstart : when starting box selection
  • boxend : when ending box selection
  • boxselect : triggered on elements when selected by box selection
  • box : triggered on elements when inside the box on boxend

Is there an event that is fired after box selection has ended and all the elements are selected?

boxend in this case is fired after the box selection has ended, but before the elements are selected. Please refer to example #1 and example #2.

boxselect is fired only after boxend is fired, as shown in example #2. In fact, it is fired once for every element that is selected, whereas we are interested in an event that is fired once after all the elements have been selected by the box.

Example 1

In the following example we run a simple cytoscape app, and set a breakpoint at cy.on('boxend', ...). It stops right after boxend event is fired. Notice in the gif that the nodes 4 and 5 are not selected, or else their color would have been changed.

bug

Example 2

In the following example we set the event handlers to be:

let boxNodeData;

cy.on('boxstart', event => {
  console.log('boxstart');
  boxNodeData = [];
})

cy.on('box', 'node', event => {
  console.log('pushed');
  boxNodeData.push(event.target._private.data);
})

cy.on('boxend', event => {
  console.log('boxend');
  console.log(boxNodeData);
})

When we run that example, the following is shown in the console:
capture

"pushed" is printed after "boxend", which means that the box events are fired after the box selection ends. Therefore, boxend can't be used for this particular purpose.

How to get cy ref with typescript?

The docs indicate this technique for straight jsx:

class MyApp extends React.Component {
  render() {
    return <CytoscapeComponent cy={(cy) => { this.cy = cy }}>;
  }
}

When I change my component to *.tsx, I get errors like TS2339: Property 'cy' does not exist on type 'MyApp'.

How can this be adapted so typescript won't complain for the class component?

All nodes are accumulated in one position.

When creating a path using dagre, the whole nodes accumulate in one position. How can we set default positions for nodes ( Cytoscape js without react works fine) instead of setting position separately using position attribute for nodes.

const layout = {
name: "dagre",
rankDir: "LR"
}
pageData = < CytoscapeComponent
elements = {
CytoscapeComponent.normalizeElements({
nodes: nodess,
edges: edgess,
layout: layout,
})
}
pan = {
{
x: 200,
y: 200
}
}
autounselectify = {
true
}
userZoomingEnabled = {
false
}
boxSelectionEnabled = {
false
}
style = {
{
width: "1200px",
height: "1000px"
}
}
/>
return (
< div

{
pageData
}
< /div>
);

/------------------------------/
Expected Result
Expected Result

Current Result
Current-Result

Node labels not visible when using stylesheet prop

Whenever I include the stylesheet prop, node labels do not show. Is this expected behaviour?

Here's a quick example using some code from the examples in the repo's README:

<CytoscapeComponent
  style={{
    width: "600px",
    height: "600px"
  }}
  stylesheet={[
    {
      selector: "node",
      style: {
        width: 20,
        height: 20,
        shape: "rectangle"
      }
    },
    {
      selector: "edge",
      style: {
        width: 15
      }
    }
  ]}
  elements={CytoscapeComponent.normalizeElements({
    nodes: [
      {
        data: { id: "one", label: "Node 1" },
        position: { x: 0, y: 0 }
      },
      {
        data: { id: "two", label: "Node 2" },
        position: { x: 100, y: 0 }
      }
    ],
    edges: [
      {
        data: {
          source: "one",
          target: "two",
          label: "Edge from Node1 to Node2"
        }
      }
    ]
  })}
/>

The node labels do not appear. Removing the stylesheet prop results in the node labels reappearing. This issue is visible in a number of screenshots/gifs from various issues on this repo, although no one has mentioned it.

FindDomNode deprecation warning with StrictMode

I'm using this package in a newly created Reactjs project with create-react-app, which creates the project using React.StrictMode. Because of this I have the following warning:

Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of t which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://fb.me/react-strict-mode-find-node

It should be updated as the Reactjs docs recommend, I don't think it would be better to remove the StrictMode just for a single component.

Immutable diff

Using the immutable diff described below does not render anything for react cytoscape. Had to change it to objectA !== objectB, however loading a complete different set of nodes with similar id's causes a problem

diff = (objectA, objectB) =>{
return objectA === objectB;
}

How to pass an array of styles like Cytoscape needs .. the react takes a regular style

I am trying to make arrows on the edges and crtoscape uses styles.. and an array of styles.. but this React wrapper seems to leverage the React style.. how to pass the array so I can get the arrow?

{
"selector": "node",
"style": {
"text-valign": "center",
"text-halign": "left",
"width": 16,
"height": 16
}
}, {
"selector": "node[type]",
"style": {
"label": "data(type)"
}
}, {
"selector": "edge",
"style": {
"width": 1,
"curve-style": "straight"
}
}, {
"selector": "edge[arrow]",
"style": {
"target-arrow-shape": "data(arrow)"
}
}, {
"selector": "edge.hollow",
"style": {
"target-arrow-fill": "hollow"
}
}]

[Question] - How to set target-arrow-shape style

Could someone help me figure out how to set the target-arrow-shape style?

The below snippet doesn't seem to work:

const elements = [
            { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
            { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
            {
                data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' },
                style: {'target-arrow-shape': 'triangle'}
            }
        ];

<CytoscapeComponent elements={elements} layout={layout} cy={cy => this._cy(cy)}
                                        style={{position: 'absolute', width: '100%', height: '100%'}}/>

Above just shows:

image

I've found out that if any of the source or target nodes are compound nodes themselves, then the triangle arrow head is displayed properly. Sample:

const elements = [
            { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
            { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
            { data: { id: 'three', label: 'Node 3', parent: 'two' }, position: { x: 200, y: 0 } },
            {
                data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' },
                style: {'target-arrow-shape': 'triangle'}
            }
        ];

Now the arrow head is displayed:
image

Any help you can give me on why it works like this is appreciated!

Edges are not dynamically updated.

When I make changes to elements variable, nodes are newly being created well but not edges.
I logged out the result but edges exist with exact source and target.
What is the issue occurring this?

[Question] How to test using Jest?

Hi!

I am attempting to write tests for my code that uses react-cytoscapejs using the Jest testing framework but can't get it working. I have replicated the setup and teardown procedure of the mocha tests you provide within my own test (see below) and this is the error I am getting:

    TypeError: Cannot read property 'h' of undefined

      62 |     document.body.appendChild(root);
      63 | 
    > 64 |     ReactDOM.render(
         |              ^
      65 |       React.createElement(TestComponent, {
      66 |         setStateRef: ref => (setState = ref),
      67 |         defaults: json

      at Layout.Object.<anonymous>.GridLayout.run (../../node_modules/cytoscape/dist/cytoscape.cjs.js:20308:10)
      at setElesAndLayout (../../node_modules/cytoscape/dist/cytoscape.cjs.js:17905:27)
      at ../../node_modules/cytoscape/dist/cytoscape.cjs.js:17917:5
      at loadExtData (../../node_modules/cytoscape/dist/cytoscape.cjs.js:17864:7)
      at new Core (../../node_modules/cytoscape/dist/cytoscape.cjs.js:17908:3)
      at new cytoscape (../../node_modules/cytoscape/dist/cytoscape.cjs.js:30747:12)
      at t.value (../../node_modules/react-cytoscapejs/dist/react-cytoscape.js:1:3894)
      at commitLifeCycles (../../node_modules/react-dom/cjs/react-dom.development.js:17334:22)
      at commitAllLifeCycles (../../node_modules/react-dom/cjs/react-dom.development.js:18736:7)
      at HTMLUnknownElement.callCallback (../../node_modules/react-dom/cjs/react-dom.development.js:149:14)
      at Object.invokeGuardedCallbackDev (../../node_modules/react-dom/cjs/react-dom.development.js:199:16)
      at invokeGuardedCallback (../../node_modules/react-dom/cjs/react-dom.development.js:256:31)
      at commitRoot (../../node_modules/react-dom/cjs/react-dom.development.js:18948:7)
      at ../../node_modules/react-dom/cjs/react-dom.development.js:20418:5
      at Object.unstable_runWithPriority (../../node_modules/scheduler/cjs/scheduler.development.js:255:12)
      at completeRoot (../../node_modules/react-dom/cjs/react-dom.development.js:20417:13)
      at performWorkOnRoot (../../node_modules/react-dom/cjs/react-dom.development.js:20346:9)
      at performWork (../../node_modules/react-dom/cjs/react-dom.development.js:20254:7)
      at performSyncWork (../../node_modules/react-dom/cjs/react-dom.development.js:20228:3)
      at requestWork (../../node_modules/react-dom/cjs/react-dom.development.js:20097:5)
      at scheduleWork (../../node_modules/react-dom/cjs/react-dom.development.js:19911:5)
      at scheduleRootUpdate (../../node_modules/react-dom/cjs/react-dom.development.js:20572:3)
      at updateContainerAtExpirationTime (../../node_modules/react-dom/cjs/react-dom.development.js:20600:10)
      at updateContainer (../../node_modules/react-dom/cjs/react-dom.development.js:20657:10)
      at ReactRoot.Object.<anonymous>.ReactRoot.render (../../node_modules/react-dom/cjs/react-dom.development.js:20953:3)
      at ../../node_modules/react-dom/cjs/react-dom.development.js:21090:14
      at unbatchedUpdates (../../node_modules/react-dom/cjs/react-dom.development.js:20459:10)
      at legacyRenderSubtreeIntoContainer (../../node_modules/react-dom/cjs/react-dom.development.js:21086:5)
      at Object.render (../../node_modules/react-dom/cjs/react-dom.development.js:21155:12)
      at Object.render (src/tests/learningTests/cytoscape.test.js:64:14)

    console.error ../../node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/virtual-console.js:29
      Error: Uncaught [TypeError: Cannot read property 'h' of undefined]
          at reportException (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
          at invokeEventListeners (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:209:9)
          at HTMLUnknownElementImpl._dispatch (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
          at HTMLUnknownElement.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
          at Object.invokeGuardedCallbackDev (/node_modules/react-dom/cjs/react-dom.development.js:199:16)
          at invokeGuardedCallback (/node_modules/react-dom/cjs/react-dom.development.js:256:31)
          at commitRoot (/node_modules/react-dom/cjs/react-dom.development.js:18948:7)
          at /node_modules/react-dom/cjs/react-dom.development.js:20418:5 TypeError: Cannot read property 'h' of undefined
          at Layout.Object.<anonymous>.GridLayout.run (/node_modules/cytoscape/dist/cytoscape.cjs.js:20308:10)
          at setElesAndLayout (/node_modules/cytoscape/dist/cytoscape.cjs.js:17905:27)
          at /node_modules/cytoscape/dist/cytoscape.cjs.js:17917:5
          at loadExtData (/node_modules/cytoscape/dist/cytoscape.cjs.js:17864:7)
          at new Core (/node_modules/cytoscape/dist/cytoscape.cjs.js:17908:3)
          at new cytoscape (/node_modules/cytoscape/dist/cytoscape.cjs.js:30747:12)
          at t.value (/node_modules/react-cytoscapejs/dist/react-cytoscape.js:1:3894)
          at commitLifeCycles (/node_modules/react-dom/cjs/react-dom.development.js:17334:22)
          at commitAllLifeCycles (/node_modules/react-dom/cjs/react-dom.development.js:18736:7)
          at HTMLUnknownElement.callCallback (/node_modules/react-dom/cjs/react-dom.development.js:149:14)
          at invokeEventListeners (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
          at HTMLUnknownElementImpl._dispatch (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
          at HTMLUnknownElement.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
          at Object.invokeGuardedCallbackDev (/node_modules/react-dom/cjs/react-dom.development.js:199:16)
          at invokeGuardedCallback (/node_modules/react-dom/cjs/react-dom.development.js:256:31)
          at commitRoot (/node_modules/react-dom/cjs/react-dom.development.js:18948:7)
          at /node_modules/react-dom/cjs/react-dom.development.js:20418:5
          at Object.unstable_runWithPriority (/node_modules/scheduler/cjs/scheduler.development.js:255:12)
          at completeRoot (/node_modules/react-dom/cjs/react-dom.development.js:20417:13)
          at performWorkOnRoot (/node_modules/react-dom/cjs/react-dom.development.js:20346:9)
          at performWork (/node_modules/react-dom/cjs/react-dom.development.js:20254:7)
          at performSyncWork (/node_modules/react-dom/cjs/react-dom.development.js:20228:3)
          at requestWork (/node_modules/react-dom/cjs/react-dom.development.js:20097:5)
          at scheduleWork (/node_modules/react-dom/cjs/react-dom.development.js:19911:5)
          at scheduleRootUpdate (/node_modules/react-dom/cjs/react-dom.development.js:20572:3)
          at updateContainerAtExpirationTime (/node_modules/react-dom/cjs/react-dom.development.js:20600:10)
          at updateContainer (/node_modules/react-dom/cjs/react-dom.development.js:20657:10)
          at ReactRoot.Object.<anonymous>.ReactRoot.render (/node_modules/react-dom/cjs/react-dom.development.js:20953:3)
          at /node_modules/react-dom/cjs/react-dom.development.js:21090:14
          at unbatchedUpdates (/node_modules/react-dom/cjs/react-dom.development.js:20459:10)
          at legacyRenderSubtreeIntoContainer (/node_modules/react-dom/cjs/react-dom.development.js:21086:5)
          at Object.render (/node_modules/react-dom/cjs/react-dom.development.js:21155:12)
          at Object.render (/apps/linepulse/src/tests/learningTests/cytoscape.test.js:64:14)
          at Object.asyncJestLifecycle (/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
          at resolve (/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
          at new Promise (<anonymous>)
          at mapper (/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
          at promise.then (/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
          at process.internalTickCallback (internal/process/next_tick.js:77:7)
    console.error ../../node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/virtual-console.js:29
      Error: Uncaught [TypeError: Cannot read property 'destroy' of undefined]
          at reportException (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
          at invokeEventListeners (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:209:9)
          at HTMLUnknownElementImpl._dispatch (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
          at HTMLUnknownElement.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
          at Object.invokeGuardedCallbackDev (/node_modules/react-dom/cjs/react-dom.development.js:199:16)
          at invokeGuardedCallback (/node_modules/react-dom/cjs/react-dom.development.js:256:31)
          at safelyCallComponentWillUnmount (/node_modules/react-dom/cjs/react-dom.development.js:17176:5https://github.com/Remimstr/broken-cytoscape-test)
          at commitUnmount (/node_modules/react-dom/cjs/react-dom.development.js:17553:11) TypeError: Cannot read property 'destroy' of undefined
          at t.value (/node_modules/react-cytoscapejs/dist/react-cytoscape.js:1:4283)
          at callComponentWillUnmountWithTimer (/node_modules/react-dom/cjs/react-dom.development.js:17169:12)
          at HTMLUnknownElement.callCallback (/node_modules/react-dom/cjs/react-dom.development.js:149:14)
          at invokeEventListeners (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
          at HTMLUnknownElementImpl._dispatch (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
          at HTMLUnknownElement.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
          at Object.invokeGuardedCallbackDev (/node_modules/react-dom/cjs/react-dom.development.js:199:16)
          at invokeGuardedCallback (/node_modules/react-dom/cjs/react-dom.development.js:256:31)
          at safelyCallComponentWillUnmount (/node_modules/react-dom/cjs/react-dom.development.js:17176:5)
          at commitUnmount (/node_modules/react-dom/cjs/react-dom.development.js:17553:11)
          at unmountHostComponents (/node_modules/react-dom/cjs/react-dom.development.js:17873:7)
          at commitDeletion (/node_modules/react-dom/cjs/react-dom.development.js:17904:5)
          at commitAllHostEffects (/node_modules/react-dom/cjs/react-dom.development.js:18685:11)
          at HTMLUnknownElement.callCallback (/node_modules/react-dom/cjs/react-dom.development.js:149:14)
          at invokeEventListeners (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
          at HTMLUnknownElementImpl._dispatch (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
          at HTMLUnknownElementImpl.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
          at HTMLUnknownElement.dispatchEvent (/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
          at Object.invokeGuardedCallbackDev (/node_modules/react-dom/cjs/react-dom.development.js:199:16)
          at invokeGuardedCallback (/node_modules/react-dom/cjs/react-dom.development.js:256:31)
          at commitRoot (/node_modules/react-dom/cjs/react-dom.development.js:18913:7)
          at /node_modules/react-dom/cjs/react-dom.development.js:20418:5
          at Object.unstable_runWithPriority (/node_modules/scheduler/cjs/scheduler.development.js:255:12)
          at completeRoot (/node_modules/react-dom/cjs/react-dom.development.js:20417:13)
          at performWorkOnRoot (/node_modules/react-dom/cjs/react-dom.development.js:20346:9)
          at performWork (/node_modules/react-dom/cjs/react-dom.development.js:20254:7)
          at performSyncWork (/node_modules/react-dom/cjs/react-dom.development.js:20228:3)
          at requestWork (/node_modules/react-dom/cjs/react-dom.development.js:20097:5)
          at scheduleWork (/node_modules/react-dom/cjs/react-dom.development.js:19911:5)
          at scheduleRootUpdate (/node_modules/react-dom/cjs/react-dom.development.js:20572:3)
          at updateContainerAtExpirationTime (/node_modules/react-dom/cjs/react-dom.development.js:20600:10)
          at updateContainer (/node_modules/react-dom/cjs/react-dom.development.js:20657:10)
          at ReactRoot.Object.<anonymous>.ReactRoot.render (/node_modules/react-dom/cjs/react-dom.development.js:20953:3)
          at /node_modules/react-dom/cjs/react-dom.development.js:21090:14
          at unbatchedUpdates (/node_modules/react-dom/cjs/react-dom.development.js:20459:10)
          at legacyRenderSubtreeIntoContainer (/node_modules/react-dom/cjs/react-dom.development.js:21086:5)
          at Object.render (/node_modules/react-dom/cjs/react-dom.development.js:21155:12)
          at Object.render (/apps/linepulse/src/tests/learningTests/cytoscape.test.js:64:14)
          at Object.asyncJestLifecycle (/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
          at resolve (/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
          at new Promise (<anonymous>)
          at mapper (/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
          at promise.then (/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
          at process.internalTickCallback (internal/process/next_tick.js:77:7)
    console.error ../../node_modules/react-dom/cjs/react-dom.development.js:17117
      The above error occurred in the <t> component:
          in t (created by TestComponent)
          in TestComponent
      
      Consider adding an error boundary to your tree to customize error handling behavior.
      Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
    console.error ../../node_modules/react-dom/cjs/react-dom.development.js:17117
      The above error occurred in the <t> component:
          in t (created by TestComponent)
          in TestComponent
      
      Consider adding an error boundary to your tree to customize error handling behavior.
      Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

The test itself is this:

import React from "react";
import ReactDOM from "react-dom";
import CytoscapeComponent from "react-cytoscapejs";

const defaults = {
  global: "cy",
  id: "cy",
  style: { width: "500px", height: "500px" },
  zoom: 1,
  pan: {
    x: 0,
    y: 0
  },
  elements: [
    {
      data: { id: "a", label: "apple" },
      position: { x: 0, y: 0 },
      scratch: { _test: 1 },
      classes: "foo bar"
    },
    {
      data: { id: "b", label: "banana" },
      position: { x: 0, y: 0 },
      scratch: { _test: 2 },
      classes: "foo bar"
    },
    {
      data: { id: "c", label: "cherry" },
      position: { x: 0, y: 0 },
      scratch: { _test: 3 },
      classes: "foo bar"
    }
  ]
};

const cloneDefaults = () => JSON.parse(JSON.stringify(defaults));

class TestComponent extends React.Component {
  constructor(props) {
    super(props);
    props.setStateRef(this.setState.bind(this));
    this.state = props.default;
  }

  render() {
    return React.createElement(CytoscapeComponent, this.state);
  }
}

describe.only("Component", () => {
  let root, setState, json;
https://github.com/Remimstr/broken-cytoscape-test
  let updateCyProps = props =>
    new Promise(resolve => setState(Object.assign({}, json, props), resolve));

  beforeEach(() => {
    json = cloneDefaults();

    root = document.createElement("div");

    document.body.appendChild(root);

    ReactDOM.render(
      React.createElement(TestComponent, {
        setStateRef: ref => (setState = ref),
        defaults: json
      }),
      root
    );
  });

  afterEach(() => {
    ReactDOM.unmountComponentAtNode(root);
    document.body.removeChild(root);
  });

  it("creates Cytoscape Instance", () => {});
});

I get generally the same errors whether I run this test or try to mount the component using enzyme.

I had difficulty creating a sandbox example of this issue so I make a new repository that you can checkout and play with: https://github.com/Remimstr/broken-cytoscape-test

I would really appreciate any insights you may have on this issue!

Layout changes after using setState

Hello everyone,
There are several states on my page. When I update a state (onClick, onChange etc.) my nodes positions also change immediately. I looked at Cytoscape.js docs to avoid this via cy reference, I didn't found any useful function. Do you have any suggestions with this?

examples using hooks / useState ?

I'm trying to create a functional component with this lib, and useState hooks, but having problems dealing with the cy instance.
I need to add events to the cy instance, eg tap. but this seems to create a memory leak - now each time the graph is rendered again, there's another 'graph' created. tapping will now send 3, 4, ... etc events.

what's the best way to manage this?
do i need to put some code in to try and remove the cy.on(...) event before the next render?

Thanks!

code is like this below

import React, { useState, useEffect } from 'react';

import Cytoscape from 'cytoscape';
import CytoscapeComponent from 'react-cytoscapejs'
import cola from 'cytoscape-cola';
// import cydagre from 'cytoscape-dagre';
import { graphStyle, nodeStyle } from './graphStyle'

const layout = {
  name: 'cola'
}
Cytoscape.use(cola);


// import { DcLib } from '../utils/DcLib'


const KGraph = (props: any) => {
  const graph = props?.graph
  const [cy, setCy] = useState({})

  useEffect(() => {
    console.log('effect')
    // cy.layout(layout).run()
  })

  console.log('graph', graph)

  const initCy = (cy: any) => {
    // @ts-ignore
    console.log('initCy')
    if (!cy) {
      setCy(cy)
    }

    cy.on('tap', (event: any) => {
      console.log('tap cy', event)
      console.log('target', event.target)
    });
  }


  if (!props.graph.ready) {
    return (<div> graph here </div>)
  }

  const layoutGraph = () => {
    // @ts-ignore
    cy.layout(layout).run()
  }

  return (
    <div>
      <button onClick={() => layoutGraph()}>redo graph</button>
      <CytoscapeComponent
        cy={initCy}
        elements={graph.elements}
        style={graphStyle}
        layout={layout}
        stylesheet={nodeStyle}
      />
    </div>
  )

}

export default KGraph

Suggestion: Run layout after patch

Hi,

We are using this component in a context where users can add/remove nodes dynamically, and it seems useful to re-run the layout after patching regardless of whether the layout options change. Just wondering what you thought about having this as an option and if there are any downsides to this that I am not considering. It could potentially be an opt-in configuration in case performance is a concern.

Happy to do a PR if this is something you think is useful. Thanks for providing the component.

cy Callback not triggering

None of the console logs are getting triggered.

I have also tried updating the elements props.

class RendererWindow extends React.Component {
  constructor(props) {
    super(props);

    this.cy = null;
  }

  componentDidMount() {
    if (this.cy) {
      console.log('here');
      this.cy.nodes().on('click', (e) => {
        console.log(e);
      });
    }
  }

  render() {
    const { elements, width, height } = this.props;
    return (
      <CytoscapeComponent
        elements={elements}
        style={{ width, height }}
        cy={(cy) => { console.log('heer'); this.cy = cy; }}
      />
    );
  }
}

Workaround for `TypeError: Cannot read property 'h' of undefined` and `TypeError: Cannot read property 'indexOf' of undefined`

I ran into the same error as cytoscape/cytoscape.js#2453 when doing ReactDOM.render and snapshot testing. It works fine in the browser.

Workaround further down

// CustomCytoscape.jsx
import React, { useRef } from 'react'
import Cytoscape from 'react-cytoscapejs'

function CustomCytoscape({ headless, elements }) {
  const layout = { name: 'preset' }

  const cyRef = useRef()

  return (
    <Cytoscape
      className={classes.cytoscape}
      layout={layout}
      cy={cy => {
        cyRef.current = cy
      }}
      elements={elements}
      stylesheet={stylesheet}
    />
  )
}
// CustomCytoscape.test.js
import React from 'react'
import ReactDOM from 'react-dom'
import renderer from 'react-test-renderer'
import CustomCytoscape from '../CustomCytoscape'

describe('CustomCytoscape', () => {
  const elements = []
  it('renders without crashing', () => {
    const div = document.createElement('div')
    ReactDOM.render(
      <CustomCytoscape elements={elements} />,
      div
    )
  })

  it('matches snapshot', () => {
    const component = renderer.create(
      <CustomCytoscape elements={elements} />
    )
    let tree = component.toJSON()
    expect(tree).toMatchSnapshot()
  })
})

returns TypeError: Cannot read property 'h' of undefined

 it('renders without crashing', () => {
       8 |     const div = document.createElement('div')
    >  9 |     ReactDOM.render(<App />, div)
         |              ^
      10 |   })
      11 | 
      12 |   const component = renderer.create(<App />)

And TypeError: Cannot read property 'indexOf' of undefined

const component = renderer.create(<App />)
         |                              ^
      13 |   let tree = component.toJSON()
      14 |   expect(tree).toMatchSnapshot()
      15 | })

At the moment I cannot spend more time looking into it, but I will try to look into it when I can.

Workaround

// App.jsx
const App = () => {
  const elements = []
  const headless = process.env.NODE_ENV === 'test'

  return (
    <div className={classes.container}>
      <CustomCytoscape elements={elements} headless={headless} />
    </div>
  )
}
// CustomCytoscape.jsx
import React, { useEffect, useRef } from 'react'
import cytoscape from 'cytoscape'
import Cytoscape from 'react-cytoscapejs'

function CustomCytoscape({ headless, elements }) {
  const layout = { name: 'preset' }

  const cyRef = useRef()

  // Use cytoscape (non-react) if headless === true
  useEffect(() => {
    if (headless) {
      cyRef.current = cytoscape({
        container: document.getElementById('cyHeadless'),
        layout,
        elements,
        style: stylesheet
      })
    }
  }, [headless, elements, layout])

  return headless ? (
    <div id="cyHeadless" className={classes.cytoscape} />
  ) : (
    <Cytoscape
      className={classes.cytoscape}
      layout={layout}
      cy={cy => {
        cyRef.current = cy
      }}
      elements={elements}
      stylesheet={stylesheet}
    />
  )
}

The following tests now works

CustomCytoscape.test.js
import React from 'react'
import ReactDOM from 'react-dom'
import renderer from 'react-test-renderer'
import CustomCytoscape from '../CustomCytoscape'

describe('CustomCytoscape', () => {
  const elements = []
  it('renders without crashing', () => {
    const div = document.createElement('div')
    ReactDOM.render(
      <CustomCytoscape elements={elements} headless={true} />,
      div
    )
  })

  it('matches snapshot', () => {
    const component = renderer.create(
      <CustomCytoscape elements={elements} headless={true} />
    )
    let tree = component.toJSON()
    expect(tree).toMatchSnapshot()
  })
})

Are positions required in order for nodes to be laid out correctly?

I have tried cose-bilkent and circle layouts. Both render all of the nodes and edges, but all of the connected nodes are laid out on top of each other. I don't remember having to do anything special with the elements when using a layout in the original cytoscape.js library.

The layout will be performed if the react component is rerendered.

How to get nested nodes width and height

Hi I am using cytoscape with compound nodes with the usage of cytoscape-extend-collapse. On before expand I would like to know what is the bounding box of children, I checked with autoWidth and height but they seem to be undefined. Is there a way to tell? My piece of code:
cy={cy => {
setCy(cy);
cy.nodes().on('expandcollapse.beforeexpand', e => {
const targetPosition = e.target.position();
const targetBoundingBox = ???;
});
}}

How to use built-in Cytoscape event responses?

Are built-in Cytoscape events (e.g., cy.on('layoutend', ... ) supported by this React Component? How can I instruct the program to respond to such events in a React-Cytoscape web app? I'm new to React and am looking for advice! Thanks.

How to use complex options in stylesheet?

Due to javascript rejecting hyphenated names, and the stylesheet prop not reflecting anything supplied with a key in quotations, I'm unable to use complex keys, such as target-arrow-shape.

Is there any indication as to how this can be done?

Thank you in advance!

Drag line

Hi,
I would like to ask if it is possible, using this library, for the user to drag lines from a node to another in order to create the edges.
Thank you in advance!

Elements prop is unable to accept objects

Description

I tried to work through a few examples, and one problem that came up was that the element prop of the react version only accepts an array of objects, whereas the original implementation accepts both arrays and objects. Particularly, objects contain specifications for nodes and edges, for example:

{
"nodes": [{...}, {...}, ..., {...}],
"edges": [{...}, {...}, ..., {...}]
}

Whereas the only format accepted (for the element prop) is the following:

[
{"data": {...}, "position": {...}, ...},
{"data": {...}, "position": {...}, ...},
...
{"data": {...}, "position": {...}, ...}
]

Here's an example of elements in the object (1st) format

Here's an example of elements in the list (2nd) format.

Files

data.json is directly downloaded from the tokyo railway example

App.js is written as such:

import React, { Component } from 'react';
import CytoscapeComponent from './react-cytoscapejs/src/component.js';
import data from './data.json'

class App extends Component {
  constructor(props) {
        super(props);
    }

    render() {
        return <CytoscapeComponent
            elements={data.elements}
        />
    }
}

export default App;

Error Output

The console output error is:

Uncaught TypeError: arr.forEach is not a function
    at forEach (json.js:5)
    at patchElements (patch.js:86)
    at patch (patch.js:12)
    at component.js:43
    at Core.batch (cytoscape.cjs.js:13675)
    at CytoscapeComponent.updateCytoscape (component.js:42)
    at CytoscapeComponent.componentDidMount (component.js:34)
    at commitLifeCycles (react-dom.development.js:14684)
    at commitAllLifeCycles (react-dom.development.js:15904)
    at HTMLUnknownElement.callCallback (react-dom.development.js:145)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:195)
    at invokeGuardedCallback (react-dom.development.js:248)
    at commitRoot (react-dom.development.js:16074)
    at completeRoot (react-dom.development.js:17462)
    at performWorkOnRoot (react-dom.development.js:17390)
    at performWork (react-dom.development.js:17294)
    at performSyncWork (react-dom.development.js:17266)
    at requestWork (react-dom.development.js:17154)
    at scheduleWork (react-dom.development.js:16948)
    at scheduleRootUpdate (react-dom.development.js:17636)
    at updateContainerAtExpirationTime (react-dom.development.js:17663)
    at updateContainer (react-dom.development.js:17690)
    at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.development.js:17956)
    at react-dom.development.js:18096
    at unbatchedUpdates (react-dom.development.js:17517)
    at legacyRenderSubtreeIntoContainer (react-dom.development.js:18092)
    at Object.render (react-dom.development.js:18151)
    at Object../src/index.js (index.js:7)
    at __webpack_require__ (bootstrap fe0e27dab2a44bac186c:678)
    at fn (bootstrap fe0e27dab2a44bac186c:88)
    at Object.0 (registerServiceWorker.js:117)
    at __webpack_require__ (bootstrap fe0e27dab2a44bac186c:678)
    at ./node_modules/ansi-regex/index.js.module.exports (bootstrap fe0e27dab2a44bac186c:724)
    at bootstrap fe0e27dab2a44bac186c:724
index.js:2178 The above error occurred in the <CytoscapeComponent> component:
    in CytoscapeComponent (at App.js:11)
    in App (at src/index.js:7)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

Here is the screenshot of react app output:
capture1
capture2
capture3
capture 4

Reproducing Error

Steps toward reproducing the error in command line:

# Create the app
npx create-react-app cytoscape
cd cytoscape

# Add dependencies
yarn add cytoscape
cd src
git clone https://github.com/plotly/react-cytoscapejs.git

# Here, replace App.js with the file described in the issue.
# Also add data.json if required

cd ..
yarn build
yarn start

Project Status?

Hi @maxkfranz, @alexcjohnson, @chriddyp, @jackparmer,

In context of #55, a group of open source contributors in the Layer5 community are considering using react-cytoscape. As we consider this, I would like to connect with you to understand how we can best collaborate and support the project; whether the project is still active and so on. Could you speak to the state of this effort?

Thanks!
- @leecalcote, @anirudhjain75, @kushthedude, @vineethvanga18, @josegonzalez @josegonzalez @dhruv0000

"Uncaught ReferenceError: cy is not defined"

I cannot access the underlying Cytoscape instance via Reacts ref API. I get the following error in the console -> Uncaught ReferenceError: cy is not defined

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.cy;
  }

  componentDidUpdate() {
    console.log(this.cy);
  }

  render() {
    return <CytoscapeComponent cy={ (cy) = this.cy = cy } elements={mockData} style={ { width: '600px', height: '600px' } } />;
  }
}

ReactDOM.render(<Container />, document.querySelector('#container'));

Upon inspecting the component.js file, I don't see any of the suggested syntax found in the React Docs. Am I doing something wrong?

Basic Example can't be Reproduced

Currently trying to run the basic example on react-cytoscapejs version 1.0.0 using create-react-app. Here's what i have done:

npx create-react-app cytoscape-basic-app
cd cytoscape-basic-app
yarn add [email protected]

At this point, we change the src/App.js file with the following code modified from the basic example:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import CytoscapeComponent from 'react-cytoscapejs';

class App extends Component {
  constructor(props){
    super(props);
  }

  render(){
    const elements = [
       { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
       { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
       { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
    ];

    return <CytoscapeComponent elements={elements} style="width: 600px; height: 600px;" />;
  }
}

export default App;

Running yarn start returns the following error:

./src/App.js
Module not found: Can't resolve 'react-cytoscapejs' in 'C:\Users\xingh\temp\test_cytoscape\cytoscape-app\src'

I solved this by changing the import statement to be:

import CytoscapeComponent from 'react-cytoscapejs/src';

Then the error became:

The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.
    in div (created by CytoscapeComponent)
    in CytoscapeComponent (at App.js:17)
    in App (at src/index.js:7)

which was solved by changing the return statement for the render() method to:

return <CytoscapeComponent elements={elements} style={{width: "600px", height: "600px"}} />;

Which compiled successfully.

I suggest updating the readme to take those problems into account. Let me know if I should start a PR. Thank you!

Distance from Node property isn't updated correctly

Description

Documentation about distance from node: http://js.cytoscape.org/#style/edge-endpoints

source-distance-from-node and target-distance-from-node don't seem to be updating correctly in React. When we update their state with setState, the changes aren't reflected immediately unless we change another property (e.g. layout) or if we move the nodes.

Here's a screenshot of the problem:
bug

Files

App.js is written as such:

import React, { Component } from 'react';
import CytoscapeComponent from './react-cytoscapejs/src/component.js';
import Select from 'react-select';

const layoutOptions = [
  { value: 'preset', label: 'preset' },
  { value: 'random', label: 'random' },
  { value: 'grid', label: 'grid' },
  { value: 'circle', label: 'circle' }
];

const elements = [
    {
        data: {id: 'one', label: 'Node 1'},
        position: {x: 50, y: 50}
    },
    {
        data: {id: 'two', label: 'Node 2'},
        position: {x: 200, y: 200}
    },
    {
        data: {id: 'three', label: 'Node 3'},
        position: {x: 100, y: 150}
    },
    {
        data: {id: 'four', label: 'Node 4'},
        position: {x: 400, y: 50}
    },
    {
        data: {id: 'five', label: 'Node 5'},
        position: {x: 250, y: 100}
    },
    {
        data: {id: 'six', label: 'Node 6', parent: 'three'},
        position: {x: 150, y: 150}
    },

    {data: {
        source: 'one',
        target: 'two',
        label: 'Edge from Node1 to Node2'
    }},
    {data: {
        source: 'one',
        target: 'five',
        label: 'Edge from Node 1 to Node 5'
    }},
    {data: {
        source: 'two',
        target: 'four',
        label: 'Edge from Node 2 to Node 4'
    }},
    {data: {
        source: 'three',
        target: 'five',
        label: 'Edge from Node 3 to Node 5'
    }},
    {data: {
        source: 'three',
        target: 'two',
        label: 'Edge from Node 3 to Node 2'
    }},
    {data: {
        source: 'four',
        target: 'four',
        label: 'Edge from Node 4 to Node 4'
    }},
    {data: {
        source: 'four',
        target: 'six',
        label: 'Edge from Node 4 to Node 6'
    }},
    {data: {
        source: 'five',
        target: 'one',
        label: 'Edge from Node 5 to Node 1'
    }},
];


class App extends Component {
    state = {
      selectedLayout: 'preset',
      sourceDistance: 0,
      targetDistance: 0,
      layout: 'preset',
      style: {width:'100%', height: '65vh'}
    }

    updateLayout = (newOption) => {
      this.setState({
        selectedLayout: newOption,
        layout: newOption.value
      });
      console.log(`Option selected:`, newOption);
    }

    updateSourceDistance = event => {
      if (event.target.value){
        this.setState({
          sourceDistance: event.target.value
        })
      }
      console.log(`New Source Distance:`, event.target.value);
    }

    updateTargetDistance = event => {
      if (event.target.value){
        this.setState({
          targetDistance: event.target.value
        })
      }
      console.log(`New Target Distance:`, event.target.value);
    }

    render() {
      const {
        selectedLayout,
        style,
        layout,
        sourceDistance,
        targetDistance
       } = this.state;

      return (
        <div>
          <CytoscapeComponent
              elements={elements}
              style={style}
              layout={{name: layout}}
              stylesheet={
                [
                  {
                    "selector": "edge",
                    "style": {
                      "width": null,
                      "curve-style": "haystack",
                      "line-color": "#999999",
                      "line-style": "solid",
                      "loop-direction": "-45deg",
                      "loop-sweep": "-90deg",
                      "source-endpoint": "outside-to-node",
                      "target-endpoint": "outside-to-node",
                      "source-distance-from-node": sourceDistance,
                      "target-distance-from-node": targetDistance
                    }
                  }
                ]
              }
          />
            <p> Layout: </p>
            <Select
              value={selectedLayout}
              onChange={this.updateLayout}
              options={layoutOptions}
            />
            <p>
              Source Distance from Node:
              <input type="number" onChange={this.updateSourceDistance} />
            </p>
            <p>
              Target Distance from Node:
              <input type="number" onChange={this.updateTargetDistance} />
            </p>
        </div>
      );
    }
}

export default App;

Reproducing Error

Steps toward reproducing the error in command line:

# Create the app
npx create-react-app cytoscape-issue
cd preset

# Add dependencies
yarn add cytoscape
yarn add react-select
cd src
git clone https://github.com/plotly/react-cytoscapejs.git

# Here, replace App.js with the file described in the issue.

cd ..
yarn build
yarn start

Using cy.on()

Hi.
I started using this react-component.
The question is how can I use cy.on() for setting my own action for 'click' on nodes/edges?
I have this code and try using "cy.on()" from http://js.cytoscape.org/#core/events
Any advice?

<CytoscapeComponent elements={elements} style={style} stylesheet={stylesheet} cy={cy => this.cy = cy} />

Locked nodes

Trying out the README example which renders the nodes but not able to move the nodes individually no matter what. Entire layout (two nodes and the edge) move in unison whenever I click on either nodes. What am I missing in order to grab individual nodes and move?

import React from 'react';
import CytoscapeComponent from 'react-cytoscapejs';

export default class Cyto extends React.Component {
    constructor(props){
        super(props);
    }

    render(){
        const elements = [
            { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
            { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
            { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
        ];

        const layout = {
            name: 'grid'
        }

        return <CytoscapeComponent elements={elements} style={{ height: '600px' }} layout={layout} />;
    }
}

Fails to run example

When I try to run the example given in readme, the following is returned:

ERROR in ./src/demo/App.js
Module not found: Error: Can't resolve 'react-cytoscapejs' in 'C:\Users\xingh\git\dash-cytoscape\src\demo'
 @ ./src/demo/App.js 17:24-52
 @ ./src/demo/index.js
 @ multi ./src/demo/index.js

The following code is written in App.js:

/* eslint no-magic-numbers: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
import CytoscapeComponent from 'react-cytoscapejs';

class App extends React.Component {
  constructor(props){
    super(props);
  }

  render(){
    const elements = [
       { data: { id: 'one', label: 'Node 1' }, position: { x: 0, y: 0 } },
       { data: { id: 'two', label: 'Node 2' }, position: { x: 100, y: 0 } },
       { data: { source: 'one', target: 'two', label: 'Edge from Node1 to Node2' } }
    ];

    return <CytoscapeComponent elements={elements} style="width: 600px; height: 600px;" />;
  }
};

export default App;

and ran in index.js (by calling NPM run start):

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

@chriddyp

Can't make Cytoscape.use() work with Gatsby

Where would you put the Cytoscape.use(), when using this library with Gatsby? The registration of extensions work fine for the standard cytoscape, but using this library I get the following error:
Uncaught TypeError: _cy.cxtmenu is not a function

import React from "react"
import CytoscapeComponent from "react-cytoscapejs"
import Cytoscape from "cytoscape"
import cxtmenu from "cytoscape-cxtmenu"
Cytoscape.use(cxtmenu)
const Cyto = () => {
  return (
    <CytoscapeComponent
      cy={cy => {
        cy.cxtmenu({
          selector: "core",
          commands: [
            {
              content: "Add",
              select: () => {
                console.log("add")
              },
              fillColor: "#0F0",
            },
          ],
        })
      }}
    />
  )
}
export default Cyto

Add update callback?

When the graph is edited (i.e. a node is moved) the passed props never changes in the parent.

Perhaps there can be a onElementChange callback so that the parent can update the elements state

TypeError: Cannot read property 'x' of undefined

Using plain JavaScript cytoscape can render composite structures:

cytoscape({
    container: document.getElementById('cy'),
    elements:[
      { data: { id: 'a', parent: 'b' } },
      { data: { id: 'b' } },
      { data: { id: 'c', parent: 'b' } },
      { data: { id: 'd' } },
      { data: { id: 'e' } },
      { data: { id: 'f', parent: 'e' } },
      { data: { id: 'ad', source: 'a', target: 'd' } },
      { data: { id: 'eb', source: 'e', target: 'b' } }
    ],
    layout: { name: "grid" }
  });

Working example in https://codepen.io/anon/pen/Oqdqjj

But when using react-cytoscapejs:

import * as React from 'react';
import CytoscapeComponent from 'react-cytoscapejs';

class App extends React.Component {
  public render() {
    return (
      <CytoscapeComponent 
        elements={ 
          [
            { data: { id: 'a', parent: 'b' } },
            { data: { id: 'b' } },
            { data: { id: 'c', parent: 'b' } },
            { data: { id: 'd' } },
            { data: { id: 'e' } },
            { data: { id: 'f', parent: 'e' } },
            { data: { id: 'ad', source: 'a', target: 'd' } },
            { data: { id: 'eb', source: 'e', target: 'b' } }
          ]
        } 
        layout={ {name: "grid"} }
      />
    );
  }
}

export default App;

I'm getting exception:
image

Cytoscape 3.5 compatability

Hey, I'm having trouble upgrading to an underlying cytoscape 3.5 version with a Dagre layout. Have the modules changed their API's for registering layouts or is there a reason you are specifically on 3.2.19? Even upgrading to 3.2.20 breaks compatability.

[Discussion] Good practice for event handling

Is there event handler for this react wrapper?

In cytoscape.js events are handled in a jquery manner: http://js.cytoscape.org/#events

However, event handling in react is quite different. For example, we would use props such as onTap, onClick, etc. which are fired when the corresponding event happens. Should those props be added, or should we use the cy.on() to handle those events?

[Question] setting elements property via setState causes graph nodes to stack on top of each other

I have a simple react app that makes a REST GET call and uses the result to display a few nodes. However, all the nodes end up stacked on top of each other at the top left corner. Apologies in advance as I'm a web newbie.

import React, {Component} from 'react';
import Cytoscape from 'cytoscape';
import './App.css';
import CytoscapeComponent from 'react-cytoscapejs';
import cola from 'cytoscape-cola';
import Pace from 'react-pace-progress';

Cytoscape.use(cola);

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            isLoading: true,
            graph: [],
            elements: [],
        };
    }

    render() {
        const layout = {
            name: 'cola',
            padding: 50
        }
        return (
            <div>
                {this.state.isLoading ? <Pace color="#27ae60"/> :
                    <CytoscapeComponent elements={this.state.elements} layout={layout}
                                        style={{position: 'absolute', width: '100%', height: '100%'}}/>}

            </div>
        );
    }

    componentDidMount() {

        fetch(`http://127.0.0.1:5000/jil/all`)
            .then(response => response.json())
            .then(result => this.setState({elements: this.buildJobGraph(result)}))

        this.setState({isLoading: false});
        
    }

    buildJobGraph(items) {
        let jobGraph = [];
        items.forEach(e => {
            if (e['type'] === 'box') {
                jobGraph.push(
                    {
                        data: {'id': e['name'], 'label': e['name']},
                        style: {'shape': 'rectangle', width: '280px', 'text-valign': 'center', 'text-halign': 'center'}
                    }
                );
            }
        });
        console.log(jobGraph);
        return jobGraph;
    }

    /*sleep = (milliseconds) => {
        return new Promise(resolve => setTimeout(resolve, milliseconds))
    }*/
}

export default App;

And this is what I end up with:

image

I then have to drag the items to the middle and spread them out:

image

I believe the cola layout is not taking effect. Any help you can give me would be immensely appreciated.

cytoscape should be a peer dependency

Rather than bundling cytoscape, it would be nice to express it as a peer dependency pegged to the major version. I'm happy to provide a PR for this.

Cannot install

I cannot install this libraray.

If I run : npm install react-cytoscapejs

I get :

"npm ERR! EINVAL: invalid argument, read"

I already have
"cytoscape": "3.10.2",
in my project

Extensions not registering (react + mobx)

I copied example with "cytoscape-cose-bilkent" from your readme and have a error No such layout 'cose-bilkent' found. Did you forget to import it and 'cytoscape.use()' it?
With "cytoscape.js-popper" i have a error .popper is not a function.
With few other extensions a have the same problems :( Could someone help to understand where problem is?

I use:
"cytoscape": "3.16.2",
"react-cytoscapejs": "1.2.1",
"react": "16.13.1",
"mobx": "5.15.5"

webpack build causes errors with server-side rendering

When we use this library in a server-side rendering environment, we get the error ReferenceError: window is not defined

if you look at the source, it looks like the error is at line 1:456 which maps to where window is referenced in this formatted version.

image

This means window is referenced on module import, so even if we wait to render the component until it is mounted (i.e., in the browser) we get this error from module import alone. We have a work around to lazy load the module when a parent component is mounted, but it's a lot of setup just to work.

This SO question suggests that it may be your webpack output configuration.

Can't Change Layout to Preset

Description

Whenever the layout is changed to something else (random, grid, etc.), the component is rendered correctly. however, whenever it is changed back to preset, nothing happens (it stays stuck in the previous layout).

44219898-229af580-a14b-11e8-92c4-44df8d42e12b

Files

App.js is written as such:

import React, { Component } from 'react';
import CytoscapeComponent from './react-cytoscapejs/src/component.js';
import Select from 'react-select';

const options = [
  { value: 'preset', label: 'preset' },
  { value: 'random', label: 'random' },
  { value: 'grid', label: 'grid' },
  { value: 'circle', label: 'circle' }
];

const elements = [
    {
        data: {id: 'one', label: 'Node 1'},
        position: {x: 50, y: 50}
    },
    {
        data: {id: 'two', label: 'Node 2'},
        position: {x: 200, y: 200}
    },
    {
        data: {id: 'three', label: 'Node 3'},
        position: {x: 100, y: 150}
    },
    {
        data: {id: 'four', label: 'Node 4'},
        position: {x: 400, y: 50}
    },
    {
        data: {id: 'five', label: 'Node 5'},
        position: {x: 250, y: 100}
    },
    {
        data: {id: 'six', label: 'Node 6', parent: 'three'},
        position: {x: 150, y: 150}
    },

    {data: {
        source: 'one',
        target: 'two',
        label: 'Edge from Node1 to Node2'
    }},
    {data: {
        source: 'one',
        target: 'five',
        label: 'Edge from Node 1 to Node 5'
    }},
    {data: {
        source: 'two',
        target: 'four',
        label: 'Edge from Node 2 to Node 4'
    }},
    {data: {
        source: 'three',
        target: 'five',
        label: 'Edge from Node 3 to Node 5'
    }},
    {data: {
        source: 'three',
        target: 'two',
        label: 'Edge from Node 3 to Node 2'
    }},
    {data: {
        source: 'four',
        target: 'four',
        label: 'Edge from Node 4 to Node 4'
    }},
    {data: {
        source: 'four',
        target: 'six',
        label: 'Edge from Node 4 to Node 6'
    }},
    {data: {
        source: 'five',
        target: 'one',
        label: 'Edge from Node 5 to Node 1'
    }},
];


class App extends Component {
    state = {
      selectedOption: 'preset',
      layout: 'preset',
      style: {width:'800px', height: '500px'}
    }

    handleChange = (selectedOption) => {
      this.setState({
        selectedOption: selectedOption,
        layout: selectedOption.value
      });
      console.log(`Option selected:`, selectedOption);
    }

    render() {
      const { selectedOption, style, layout } = this.state;

      return (
        <div>
          <CytoscapeComponent
              elements={elements}
              style={style}
              layout={{name: layout}}
          />
          <Select
            value={selectedOption}
            onChange={this.handleChange}
            options={options}
          />
        </div>
      );
    }
}

export default App;

Reproducing Error

Steps toward reproducing the error in command line:

# Create the app
npx create-react-app preset
cd preset

# Add dependencies
yarn add cytoscape
yarn add react-select
cd src
git clone https://github.com/plotly/react-cytoscapejs.git

# Here, replace App.js with the file described in the issue.

cd ..
yarn build
yarn start

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.