Giter VIP home page Giter VIP logo

react-cytoscapejs's Issues

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"
}
}]

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 = ???;
});
}}

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; }}
      />
    );
  }
}

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.

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;
}

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.

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;

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

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

"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?

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

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

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

[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!

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!

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.

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

[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!

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!

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.

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

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?

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.

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

[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?

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} />;
    }
}

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

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

[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.

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()
  })
})

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?

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

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 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.

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?

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"

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

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} />

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.

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!

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.

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.