Closed clemsos closed 6 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.
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.
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
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:
===
checks for which elements and which top-level fields (e.g. data
, position
, ...) need to be updated. cy.json()
or whole elements in ele.json()
-- only diffs, i.e. if( oldEleJson.data !== newEleJson.data ){ ele.json( newEleJson ); }
. If you use the naive approach and update everything on every render()
, you will cause needless overhead with additional style calculations, events, etc. newEleJson
objects if your data
in your elements contain nested objects (e.g. if you mutate ele.data()
outside the React component). If only React is driving writes, then there is no need for cloning (and Cytoscape does no cloning internally).cytoscape()
in your render method. That's a huge mistake. You want to create an instance once per component (i.e. componentWillMount()
) -- not once per render.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
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.
@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.
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.
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.
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.
@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:
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.
@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!
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.
@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);
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.
My dataset is not large and @arddor 's method works fine for me.
@keiono Any updates ?
@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.
@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.
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.
@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
@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.
Inferno doesn't support string refs either, so it's best to avoid them altogether.
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
}
@ketysek why not try removing the old elements and reusing the same graph instance?
@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
}
@keiono hi, has this effort been abandoned?
@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...?
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
There's a React component that was made in collaboration with Plotly: https://github.com/plotly/react-cytoscapejs
There's some example code on this issue: https://github.com/kaluginserg/cytoscape-node-html-label/issues/19#issuecomment-467474489
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!
Just checking in to see whether there is any plan to officially support React?
@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.
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 ?