import React from 'react'; import CytoscapeComponent from 'react-cytoscapejs'; import Popup from 'reactjs-popup'; import '../../css/Graph.css'; import Slider from '../Slider'; import GIFGenerator from './GIFGenerator'; //import GIF from '@dhdbstjr98/gif.js'; class Graph extends React.Component { constructor(props) { super(props); //this.props.animationLength = 101; this.cy = React.createRef(); this.stepTime = 0; this.neverPlayed = true; this.state ={animationDuration: 4, step: 0, playing: false, gif: null}; } componentDidMount() { //initial layout this.layoutGraph(); this.setState({step: 0}, () => { //crop animation this.visualizeOneStep(false); }); } componentDidUpdate(prevProps, _) { //only recalculate the layout if graph has changed if (prevProps.graphData !== this.props.graphData) { this.layoutGraph(); this.setState({step: 0}, () => { //first crop the animation clearInterval(this.animationId); this.setState({playing: false}); this.visualizeOneStep(false); }); } //we could check here if the simulation should start displaying } //layouting algorithm layoutGraph() { let options = { name: 'cose', // Called on `layoutready` ready: function(){}, // Called on `layoutstop` stop: function(){}, // Whether to animate while running the layout // true : Animate continuously as the layout is running // false : Just show the end result // 'end' : Animate with the end result, from the initial positions to the end positions animate: 'end', // Easing of the animation for animate:'end' animationEasing: undefined, // The duration of the animation for animate:'end' animationDuration: undefined, // A function that determines whether the node should be animated // All nodes animated by default on animate enabled // Non-animated nodes are positioned immediately when the layout starts animateFilter: function ( node, i ){ return true; }, // The layout animates only after this many milliseconds for animate:true // (prevents flashing on fast runs) animationThreshold: 250, // Number of steps between consecutive screen positions update refresh: 20, // Whether to fit the network view after when done fit: true, // Padding on fit padding: 30, // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } boundingBox: undefined, // Excludes the label when calculating node bounding boxes for the layout algorithm nodeDimensionsIncludeLabels: false, // Randomize the initial positions of the nodes (true) or use existing positions (false) randomize: false, // Extra spacing between components in non-compound graphs componentSpacing: 40, // Node repulsion (non overlapping) multiplier nodeRepulsion: function( node ){ return 2048; }, // Node repulsion (overlapping) multiplier nodeOverlap: 4, // Ideal edge (non nested) length idealEdgeLength: function( edge ){ return 32; }, // Divisor to compute edge forces edgeElasticity: function( edge ){ return 32; }, // Nesting factor (multiplier) to compute ideal edge length for nested edges nestingFactor: 1.2, // Gravity force (constant) gravity: 1, // Maximum number of steps to perform numIter: 1000, // Initial temperature (maximum node displacement) initialTemp: 1000, // Cooling factor (how the temperature is reduced between consecutive steps coolingFactor: 0.99, // Lower temperature threshold (below this point the layout will end) minTemp: 1.0 }; var layout = this.cy.layout( options ); layout.run(); } changeAnimationDuration = (e) => { this.setState({animationDuration: e.target.value, playing: false, step: 0 }, () => { this.stepTime = Math.abs(this.state.animationDuration) * 1000 / this.props.animationLength; clearInterval(this.animationId); this.visualizeOneStep(false); }); } visualizeSimulation = () => { if (this.state.playing) { clearInterval(this.animationId); this.setState({playing: false}); return; } else if (this.state.step < this.props.animationLength && !this.neverPlayed) { this.animationId = setInterval(this.visualizeOneStep, this.stepTime); this.setState({playing: true}); return; } this.neverPlayed = false; //check if we pause the animation clearInterval(this.animationId); this.setState({playing: false}); //first we need to normalize the distribution //this.props.normalize(); this.setState({step: 0}, () => { if (this.state.animationDuration <= 0) { this.setState({animationDuration: Math.abs(this.state.animationDuration)}); } this.stepTime = (this.state.animationDuration) * 1000 / this.props.animationLength; this.animationId = setInterval(this.visualizeOneStep, this.stepTime); this.setState({playing: true}); //update the button this.setState({}); }); } //this is the method to visualize the simulation visualizeOneStep = (increment = true) => { //the data of the simulation is stored in: this.props.simulationData var data = this.props.simulationData; if (data == null) { return; } if (this.state.step > this.props.animationLength) { this.finishAnimation(); return; //this.setState({step: 0}, () => { //return; //}); } //if this is null do not continue => bug if (this.props.colors == null || this.props.colors.length === 0) { console.log("no colors were given"); return; } //array of all nodes let allNodes = this.cy.elements('node'); //do one step for (var i = 0; i < allNodes.length; i++) { //the current state of the current node let state = data[this.state.step][i]; let color = this.props.colors.find((element) => { return element[0] === state; })[1]; this.cy.getElementById(allNodes[i].id()).style('background-color', color); } if (increment) { if (this.state.step < this.props.animationLength) { this.setState({step: this.state.step + 1}); } else { this.finishAnimation(); } } else { //update this.setState({}); } } finishAnimation() { //clearInterval(this.animationId); //this.setState({playing: false, step: this.props.animationLength}); } visualizeSpecificStep = (e) => { var val = Number(e.target.value); if (val < 0 || val > this.props.animationLength) { return; } //first stop the current animation clearInterval(this.animationId); this.setState({playing: false}); this.setState({step: val}, () => { this.visualizeOneStep(false); }); } render() { var playPauseString = "Play Simulation ▶️"; if (this.state.playing) { playPauseString = "Stop Simulation ⏹️"; } //show directed graph *or* chart //we want this to be a "tabbed" approach return (