Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Reset cytoscape component to its original display #33

Closed
Akronix opened this issue Feb 27, 2019 · 22 comments
Closed

[Feature request] Reset cytoscape component to its original display #33

Akronix opened this issue Feb 27, 2019 · 22 comments

Comments

@Akronix
Copy link
Contributor

Akronix commented Feb 27, 2019

Hi.

So imagine that the user does a combination of zooming, moving around and nodes dragging. And then they want to reestablish the network to its original display.

We are missing for an incorporated control that would redraw the cytoscape component to the original place, size and shape that it had before any user interaction was made.

Similarly to this, plotly graphs has this feature of double-clicking on them to reset them to their original configuration.

@Akronix Akronix changed the title [Feature request] Reset cytoscape component to original display [Feature request] Reset cytoscape component to its original display Feb 27, 2019
@xhluca
Copy link
Collaborator

xhluca commented Feb 28, 2019

Could you elaborate on what kind of interaction would be expected? At the moment it would be pretty straightforward to add a button and manually reset the Cytoscape parameters with a callback. Double-clicking on the network would break the consistency with Cytoscape.js (i.e. it will have a different behavior than what the developer/user expects).

@xhluca
Copy link
Collaborator

xhluca commented Feb 28, 2019

If there's a non-intrusive way to do it, this would be a very interesting idea! I just have a bit of trouble thinking of how to approach that.

@Akronix
Copy link
Contributor Author

Akronix commented Feb 28, 2019

hmm what about an anchor element (<a>) reading "Reset" in an overlying <div>?

@xhluca
Copy link
Collaborator

xhluca commented Feb 28, 2019

Have you tried adding that using dash html components? https://dash.plot.ly/dash-html-components

@xhluca
Copy link
Collaborator

xhluca commented Feb 28, 2019

You can probably using the "n_clicks" props of html.A as an input to a callback that will reset the zoom parameter of Cytoscape

@Akronix
Copy link
Contributor Author

Akronix commented Mar 1, 2019

I think we mean different things by reset.

By what I meant by reset is not just to reset the zooming, but also to re-establish every node to its original position, center the screen to the cytoscape component in case the user has moved around or to redraw nodes if they were removed somehow. Or maybe this is a complete redraw of the component?
I believed this could be an interesting add-on for every user though, and it could come along with the dash-cytoscape component (with the option to opt-out of course).

Well, I think we will develop the corresponding callbacks to suit our needs, at least the un-zoom feature.
Feel free to close this if you find so appropriate.

@xhluca
Copy link
Collaborator

xhluca commented Mar 1, 2019

I think I understand what you are referring to! I think that this could be accomplished with a rerendering of the Cytoscape's current properties (i.e. without taking into account the change in state). I can create a usage example and save it in the demos directory so people can use it.

I will bring this suggestion up with the product team and see if it can be added easily. If others believe this could be an interesting addition, feel free to add your thoughts in this issue. I'll leave it open for now!

@FRYoussef
Copy link

Hi,
I think it's a great idea, because sometimes I lose the network involuntarily when zooming or dragging.
I've tried to place a button whose callback gets the cytoscape layout and returns it in order to reset the zoom and the network position, but it hasn't worked

@Akronix
Copy link
Contributor Author

Akronix commented Mar 15, 2019

(Just as a background note, @FRYoussef and I are working in an webapp which does a heavy usage of dash-cytoscape)

@xhluca
Copy link
Collaborator

xhluca commented Mar 15, 2019

@FRYoussef could you share a gist or paste the callback here?

@FRYoussef
Copy link

Sure!!

import dash
import dash_cytoscape as cyto
import dash_html_components as html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)
app.layout = html.Div([
    html.Button('Reset', id='bt-reset'),
    cyto.Cytoscape(
        id='cytoscape',
        elements=[
            {'data': {'id': 'one', 'label': 'Node 1'}, 'position': {'x': 50, 'y': 50}},
            {'data': {'id': 'two', 'label': 'Node 2'}, 'position': {'x': 200, 'y': 200}},
            {'data': {'source': 'one', 'target': 'two','label': 'Node 1 to 2'}}
        ],
        layout={'name': 'cose'}
    )
])

@app.callback(
    Output('cytoscape', 'layout'),
    [Input('bt-reset', 'n_clicks')],
    [State('cytoscape', 'layout')]
)
def reset_layout(n_clicks, layout):
    print('click')
    return layout

if __name__ == '__main__':
    app.run_server(debug=True)

@xhluca
Copy link
Collaborator

xhluca commented Mar 15, 2019

In your callback, try to return elements, or even zoom instead of layout. If that doesn't work, try to return all 3 of them at the same time using the new multioutput feature (i.e. Output to "layout", "elements", and "zoom")

@FRYoussef
Copy link

I had already tested it with zoom and elements, but it doesn't work. I think this isn't going to work because elements and layout refresh the layout just with a new state, and zoom in the documentation is referenced as the initial value of the graph so you couldn't use it in a callback.
In addition, I've tried the multioutput but it didn't work.
Here the code I've used:

@app.callback(
    [Output('cytoscape', 'zoom'),
    Output('cytoscape', 'layout'),
    Output('cytoscape', 'elements')],
    [Input('bt-reset', 'n_clicks')],
    [State('cytoscape', 'layout'),
    State('cytoscape', 'elements')]
)
def reset_layout(n_clicks, layout, elements):
    print('click')
    return [1, layout, elements]

@xhluca
Copy link
Collaborator

xhluca commented Mar 29, 2019

@FRYoussef Were you able to fix this issue? Could you share a minimum working Dash app with the issue?

@FRYoussef
Copy link

No, I wasn't able.
It seems like cytoscape component is only allowed to refresh with a new state, so if you try sending its own elements, layout, etc... in a dash callback it won't update.

@xhluca
Copy link
Collaborator

xhluca commented Mar 31, 2019

Indeed, that's something that should be fixed. In theory, your method should work, as discussed in this issue. We'll investigate this further, and try to get it fixed in 0.1.0 or 0.1.1

@xhluca
Copy link
Collaborator

xhluca commented Mar 31, 2019

A current workaround is to initialize elements outside of the graph, and use it as an input to the callback (rather than using the state of Cytoscape elements):

"""
An example to show how to reset the position, zoom level, and layout of a Cytoscape graph, using a
button attached to a callback.
"""
import dash
import dash_cytoscape as cyto
import dash_html_components as html
from dash.dependencies import Input, Output

elements = [
    {'data': {'id': 'one', 'label': 'Node 1'}, 'position': {'x': 50, 'y': 50}},
    {'data': {'id': 'two', 'label': 'Node 2'}, 'position': {'x': 200, 'y': 200}},
    {'data': {'source': 'one', 'target': 'two', 'label': 'Node 1 to 2'}}
]

layout = {'name': 'grid'}

app = dash.Dash(__name__)
app.layout = html.Div([
    html.Button('Reset', id='bt-reset'),
    cyto.Cytoscape(
        id='cytoscape',
        elements=elements,
        layout=layout
    )
])


@app.callback(
    [Output('cytoscape', 'zoom'),
     Output('cytoscape', 'elements')],
    [Input('bt-reset', 'n_clicks')]
)
def reset_layout(n_clicks):
    print(n_clicks, 'click')
    return [1, elements]


if __name__ == '__main__':
    app.run_server(debug=True)

This effectively works since the ID of the edges are not defined, so are generated every time you are updating the callback. However, as you pointed out (and as I dicussed with Max in the issue linked above), Cytoscape.js only refreshes when it finds a difference in the elements that are sent to it.

@FRYoussef
Copy link

That's works good for me !!,
thanks, good job.

@xhluca
Copy link
Collaborator

xhluca commented Mar 31, 2019

Thank you.

Upon reflections, I believe that it is better not to perform an automatic position reset for elements when you update Cytoscape with a callback (for example when you output to "layout" or to "elements"). This is because it might bring unintentional position changes when you are updating other props, e.g. "stylesheet", since there is no way on the React side to detect why it was updated (i.e. whether "elements" prop was fired, or whether the "stylesheet" prop was fired).

As for the zoom prop, it should definitely change when you update it with a callback. We will try to address this in 0.1.0, or else in 0.1.1.

@xhluca xhluca added this to To do in Release v0.1.0 Mar 31, 2019
@xhluca
Copy link
Collaborator

xhluca commented Mar 31, 2019

Seems like this is not an issue. Upon further tests, I realized that userZoomingEnabled was set to False by default (shouldn't be). This will be changed to True by default in future release.

Here's an example of modifying the zoom using a slider:

import dash
import dash_cytoscape as cyto
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
from copy import deepcopy

default_elements = [
    {'data': {'id': 'one', 'label': 'Node 1'}, 'position': {'x': 50, 'y': 50}},
    {'data': {'id': 'two', 'label': 'Node 2'}, 'position': {'x': 200, 'y': 200}},
    {'data': {'source': 'one', 'target': 'two', 'label': 'Node 1 to 2'}}
]

layout = {'name': 'grid'}

app = dash.Dash(__name__)
app.layout = html.Div([
    html.Button('Reset', id='bt-reset'),
    dcc.Slider(id='slider', min=0.5, max=2, step=0.1),
    cyto.Cytoscape(
        id='cytoscape',
        elements=default_elements,
        zoomingEnabled=True,
        zoom=2,
        layout=layout
    )
])

@app.callback(Output('cytoscape', 'zoom'),
              [Input('slider', 'value')])
def update_zoom(value):
    print(value)
    return value




if __name__ == '__main__':
    app.run_server(debug=True)

@xhluca xhluca removed this from To do in Release v0.1.0 Apr 1, 2019
@xhluca
Copy link
Collaborator

xhluca commented Apr 5, 2019

@FRYoussef Can I close this issue?

@FRYoussef
Copy link

Ooh I'm sorry, sure, that works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants