cytoscape / cytoscape.js

Graph theory (network) library for visualisation and analysis
https://js.cytoscape.org
MIT License
10.09k stars 1.64k forks source link

Cytoscape /React component ? #1468

Closed clemsos closed 6 years ago

clemsos commented 8 years ago

Hi there,

I am trying to build a Cytoscape React component for one my project. I was wondering if there was anything else out there already or of I should start building one from scratch.

What I have found so far

@OAGr : you gave up on cytoscape for the latest versions of guesstimate. Is that related to React ? Do you have any feedbacks or guidelines to use Cytoscape + React together ?

OAGr commented 8 years ago

Hi clemsos,

I gave up on cytoscape because I decided I didn't want that kind of graph interface. The tool itself was relatively fine.

It's been a while since I used it, but you can see how I did there. In general I recommend wrapping cytoscape within a React component so you can treat it more naturally in React. One issue I ran into was that webpack complained a bit about it, but this didn't seem to prevent anything.

I think that file you linked to from Guesstimate could actually give you a start in how it's possible to wrap Cyoscape into a React component. In this case I made one which only exposed the functionality I cared about.

keiono commented 8 years ago

Hi clemsos.

I'm Kei from the Cytoscape Consortium. We are developing graph visualization components using Cytoscape.js, React, Redux, ES2015, Webpack, etc. We are doing this as our main project for modernizing the Cytoscape ecosystem, including both desktop and web applications. Our goal is maintaining reusable visualization components for users who want to develop component-based applications quickly.

Since Cytoscape.js project started as a jquery-based library, it is a bit tricky to create React based component. At this point, we decide to use Cytoscape.js as a simple renderer for graphs and all data management will be done in Redux side. Here is the list of things we have so far:

This is the store / reducers used by the component. Just like other Redux applications, all data will be stored here. In our case, the actual graph and tables will be stored here.

This is a thin React.js wrapper for Cytoscape.js. This uses the store above and render the network using standard react+redux architecture. Still needs to add more event handlers, but they are already in our TODO list.

This is optional if you are an experienced react/redux user, but if you use this, it can render the given data without knowing technical details inside these components. It's mainly for developers who want to integrate this component to existing applications.

Maybe you noticed already, but in this approach, graph data exists in both Redux store and Cytoscape.js. This is the limitation of our current approach, but it covers 90% of our use cases, so we decided to go this way. If you really need to handle huge networks in web browsers, you may need more dumb/simple renderer for speed and memory space, but I think Cytoscape.js is sufficient for most of real-world graph visualization projects.

Since we've just started this project, there are tons of TODOs and we need to write more documentations, but eventually we will provide more working examples and documents shortly. The following is the one I'm working on now using these components and some other UI toolkits, and looks it works fine.

component-based-cytoscape-app

Once it's ready, we'll publish the application code, too.

In any case, if you need a Cytoscape.js based graph visualization component, we are happy to help.

Thanks, Kei UC, San Diego / The Cytoscape Consortium

maxkfranz commented 8 years ago

Recommended, model approach:

Although React encourages using declarative components for everything, there's nothing stopping you from using Cytoscape normally as a model. Write your graph model in Cytoscape -- or in a module on top of Cytoscape. You can pass Cytoscape instances to components if they need to access the graph data. In this approach, you treat Cytoscape as one of your models.

Alternatively, you can use Cytoscape just as a visualisation component, underneath React. That approach follows in the next section, but it requires you to do diffing. It also means you lose out on events and many other benefits. I would suggest using Cytoscape as a model, because Cytoscape is a model -- it just so happens to have a renderer/component extension included in the default build.

Declarative, viz-only approach:

If the model (state) for the component is just JSON, you can use cy.json() and ele.json() to rerender.

In this way, your model is held completely on the React side (in JSON) --- and it is updated declaratively: Just specify the JSON diffs for the viewport and the changed elements.

You should really use Immutable to handle state with this approach (and in general, anyway). Notes:

clemsos commented 8 years ago

Thank you all for those info, super useful !

My idea is to keep cytoscape as a model and relies on the cy instance, as max mentioned. I am going to dig into those different solutions and will let you know what seems to work best

arddor commented 8 years ago

The general way I implemented cytoscape components is like the following. I have a component which renders cytoscape:

import React, {Component} from 'react';
import cytoscape from 'cytoscape';
import conf from './conf';

let cyStyle = {
  height: '400px',
  display: 'block'
};

class Cytoscape extends Component{
  cy = null;

  componentDidMount(){
    conf.container = this.refs.cyelement;
    let cy = cytoscape(conf);

    this.cy = cy;
    cy.json({elements: this.props.elements});
  }

  shouldComponentUpdate(){
    return false;
  }

  componentWillReceiveProps(nextProps){
    this.cy.json(nextProps);
  }

  componentWillUnmount(){
    this.cy.destroy();
  }

  getCy(){
    return this.cy;
  }

  render(){
    return <div style={cyStyle} ref="cyelement" />
  }
}

export default Cytoscape;

Then in a parent component:

import React, {Component} from 'react';
import Cytoscape from './Cytoscape';

class Parent extends Component{
  componentDidMount(){
    // this is a good place for events
    this.refs.graph.getCy().on(....)
  }

  render(){
    return (
        <Cytoscape ref="graph" elements={[{data: { id: 'a' }}]} />
    )
  }
}

export default Parent;

This allows you to pass down json elements and still access all other functionalities with getting access to the cy in the child component. You could for example add an event listener which on add dispatches a redux action and pass down the props.

I would be interested if anyone sees problems with this approach.

maxkfranz commented 8 years ago

@arddor You'll run into the performance issues I mentioned with larger datasets.

If props can accept non-JSON objects (i.e. with a prototype), I would just do <Cytoscape cy={someCyInstance} ... >. That way, your code can share the cy ref without being limited to using it within the components tree, and you can use cy.getElementById('foo').data('bar', 'baz') instead of more expensive cy.json() calls.

maxkfranz commented 8 years ago

Otherwise using your existing approach, you would have to diff nextProps in each componentWillReceiveProps() call using ImmutableJS to make more performant per-element ele.json( patch ) calls for larger datasets.

mmaclach commented 8 years ago

I'm working on implementing something similar using redux, but not in react. Thanks for the tips above.

What I'm looking at is keeping a cy object as the state and returning that cy object (or selections of it) to components.

madebyafox commented 8 years ago

Hi guys - this is FANTASTIC - thank you for taking the time to work on reusable components for cytoscape! I understanding you're actively developing... but wondering if you have any examples or tutorials available?

@keiono I've been trying to use the CyNetworkViewer component, but can't seem to get past the setup stage :-/ (is there an npm package for CyFramework?)

Thanks!

context: I'm working on an open source cybersecurity project involving the visualization of a MASSIVE knowledge graph. I hope to leverage cytoscape.js for its graph layout capabilities, but as we are using React - everything is more complicated.

keiono commented 8 years ago

@madebyafox

Sorry, we are experimenting with the component and still testing the component in our test application before separating it out as a complete react component.

Here is an example:

http://ci-dev-serv.ucsd.edu:8080/?url=https://dl.dropboxusercontent.com/u/161833/gal.json&style=default&bgcolor=white

This uses Cytoscape.js/React/Redux, and source code is available here:

https://github.com/idekerlab/web.cytoscape https://github.com/idekerlab/web.cytoscape/tree/master/client/components/NetworkPanel

But again, this is just a test application to see how developer can use network viewer component in real React+Redux applications. Once we satisfy with the usability of the component, I'll deploy the version 1.0.0 of the CyNetworkViewer component to npm. The version 1.0.0 will be a pure React Component, and there will be no dependency to Redux/etc.

madebyafox commented 8 years ago

@keiono Thanks for the link to your work-in-progress application, as well as the source code. This is super helpful!

We'll be eagerly awaiting your progress and news about the 1.0 release!

Flui commented 8 years ago

Hi I am also using cytoscape with React-Redux written in ES6. Has anybody wrote an cytoscape extension and got it work with react. Beacause everytime I tried to import an cytoscape extension I got the following error for registrating that extension to cytoscape "Uncaught ReferenceError: cytoscape is not defined".

Even though the variable cytoscape exists.

aindlq commented 8 years ago

@Flui I'm successfully using many cytoscape extensions with react and typescript, I assume it is quite similar to ES6.

Most of the time you need to propagate cytoscape and jquery instance into extension, as well as register jquery in cytoscape:

import * as jquery from 'jquery';
import * as cytoscape from 'cytoscape';
import * as expand_collapse from 'cytoscape-expand-collapse';

cytoscape.registerJquery(jquery);
expand_collapse(cytoscape, jquery);
maxkfranz commented 8 years ago

Since Cytoscape.js project started as a jquery-based library, it is a bit tricky to create React based component.

Originally missed this statement. This is untrue. No public release required jQuery. Cytoscape is a standalone library, and it's no more difficult to use Cytoscape with React than any other standalone lib.

Some extensions can depend on jQuery if they want to support IE. @aindlq's approach is mostly correct using ES6/ES2015 import. The problem with import is that it's not as clear as CJS require().

import cytoscape from 'cytoscape';
// is the same as
let cytoscape = require('cytoscape');
import * as cytoscape from 'cytoscape';
// is (pretty much) the same as
let cytoscape = (() => {
  let ret = {};
  let req = require('cytoscape');

  Object.assign( ret, req );

  ret.default = req;

  return ret;
})();

So you probably shouldn't be doing import * with libs that have a single, default export.

guangningyu commented 7 years ago

My dataset is not large and @arddor 's method works fine for me.

bjonnh commented 7 years ago

@keiono Any updates ?

Sigfried commented 7 years ago

@maxkfranz, I'm trying to use cytoscape.js in a React component, just sending a div ref from the component to cytoscape to use as the container. It works fine except that tapping is offset. I tried to fix that with cy.resize() as per http://js.cytoscape.org/#cy.resize, but resizing doesn't fix it.

...

In writing this comment I discovered the problem: the container div I was giving to cytoscape had something in it (an h4), so tapping was offset by the height of that. This might be counted as a doc bug? With the instructions to resize to fix tap offset, it should probably mention that container has to be empty.

maxkfranz commented 7 years ago

@Sigfried You can't put anything in the div, as a visualisation generally owns its div. That holds true for pretty much any visualisation lib, not just Cytoscape. The docs could be updated to make that explicit.

Sigfried commented 7 years ago

Thank you. Yeah, I know I was being dumb, but a doc change would have helped me. I just put in a pull request to add a sentence.

justbill2020 commented 7 years ago

@arddor what is the conf.js file you're importing in your example?

also, is there any changes you would recommend it seems that refs is advised against now i'm using react v15.6.1

alexnault commented 7 years ago

@justbill2020 Yeah, change the string ref to a standard ref since string refs are deprecated.

According to React, refs are useful for integrating with imperative libraries (like Cytoscape), so it's definitely not advised against here.

maxkfranz commented 7 years ago

Inferno doesn't support string refs either, so it's best to avoid them altogether.

ketysek commented 6 years ago

Hello, I've got one small problem with destroying cytoscope ... Imagine a list of posts, each post has its own graph ... when I click to view some post's detail, the graph is rendered, but then I go back and click on another post's detail -> at this moment, I can see the graph of previous post for a while, then it renders actual post's graph. How can I prevent this? I've adapted @arddor solution and got this in my componentWillUnmount method...nothing works

componentWillUnmount(){
    this.cy.json({ elements: undefined })
    this.cy.destroy()
    console.log("cy:", this.cy) // not destroyed yet
  }
mmaclach commented 6 years ago

@ketysek why not try removing the old elements and reusing the same graph instance?

ketysek commented 6 years ago

@mmaclach Ok, so I've tried this, but it doesn't solve the the problem

componentDidMount() {
    if(this.cy === null) {
      const container = {
        container: this.containerDOM
      }

      this.cy = cytoscape(
        Object.assign({}, container, cytoscapeDefaults)
      )
    }
  }

  componentWillUnmount() {
    this.cy.elements().remove()
  }

  shouldComponentUpdate() {
    return false
  }
byF commented 6 years ago

@keiono hi, has this effort been abandoned?

frozenL commented 6 years ago

@arddor hi, I tried to do it in your way but an error occurred:

TypeError: Heap is not a constructor new LayeredTextureCache node_modules/cytoscape/dist/cytoscape.cjs.js:24691 24688 | return b.reqs - a.reqs; 24689 | }; 24690 | 24691 | self.layersQueue = new Heap(qSort); 24692 | 24693 | self.eleTxrCache = eleTxrCache; 24694 |

in the same file, on line 24641, it has 24641 | var Heap = __webpack_require__(8);

Do you know what's going on...?

tmkasun commented 5 years ago

Hi All,

I also looking for a way to integrate cytoscape.js into a React application, Could you please provide an example for how to do that

Thanks

maxkfranz commented 5 years ago

There's a React component that was made in collaboration with Plotly: https://github.com/plotly/react-cytoscapejs

smith commented 5 years ago

There's some example code on this issue: https://github.com/kaluginserg/cytoscape-node-html-label/issues/19#issuecomment-467474489

alexkreidler commented 4 years ago

Ping @keiono @maxkfranz

Any updates on this?

It looks like @keiono had a really ambitious and exciting prototype going on for this. See https://github.com/cytoscape/cytoscape.js/issues/1468#issuecomment-235354294

Are we ever going to get officially supported React components for Cytoscape?

Thanks!

merges commented 3 years ago

Just checking in to see whether there is any plan to officially support React?

smith commented 3 years ago

@merges I don't know about any other plans, but another example that could be helpful is the service map in Elastic APM: https://github.com/elastic/kibana/blob/master/x-pack/plugins/apm/public/components/app/service_map/Cytoscape.tsx

It's specific to our use-case, and it's under the Elastic License 2.0, so there are some restrictions on how you can use the code, but hopefully it could be helpful.