Skip to content

Commit d75dff2

Browse files
committed
Dynamic graph layout switching.
1 parent eaa8acb commit d75dff2

File tree

18 files changed

+583
-822
lines changed

18 files changed

+583
-822
lines changed

NodeGraphQt/base/commands.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ def redo(self):
121121
self.model.nodes[self.node.id] = self.node
122122
self.viewer.add_node(self.node.view, self.pos)
123123

124+
# node width & height is calculated when its added to the scene
125+
# so we have to update the node model here.
126+
self.node.model.width = self.node.view.width
127+
self.node.model.height = self.node.view.height
128+
124129

125130
class NodeRemovedCmd(QtWidgets.QUndoCommand):
126131
"""

NodeGraphQt/base/graph.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717
from NodeGraphQt.base.node import NodeObject
1818
from NodeGraphQt.base.port import Port
1919
from NodeGraphQt.constants import (
20-
NODE_LAYOUT_DIRECTION,
21-
NODE_LAYOUT_HORIZONTAL,
22-
NODE_LAYOUT_VERTICAL,
2320
URI_SCHEME,
2421
URN_SCHEME,
22+
LayoutDirectionEnum,
2523
PipeLayoutEnum,
2624
PortTypeEnum,
2725
ViewerEnum
@@ -789,9 +787,20 @@ def set_pipe_style(self, style=PipeLayoutEnum.CURVED.value):
789787
style = style if 0 <= style <= pipe_max else PipeLayoutEnum.CURVED.value
790788
self._viewer.set_pipe_layout(style)
791789

790+
def layout_direction(self):
791+
"""
792+
Return the current node graph layout direction.
793+
794+
Returns:
795+
int: layout direction.
796+
"""
797+
return self.model.layout_direction
798+
792799
def set_layout_direction(self, direction):
793800
"""
794801
Sets the node graph layout direction to horizontal or vertical.
802+
This function will also override the layout direction on all
803+
nodes in the current node graph.
795804
796805
Note:
797806
By default node graph direction is set to "NODE_LAYOUT_HORIZONTAL".
@@ -804,9 +813,12 @@ def set_layout_direction(self, direction):
804813
Args:
805814
direction (int): layout direction.
806815
"""
807-
direction_types = [NODE_LAYOUT_HORIZONTAL, NODE_LAYOUT_VERTICAL]
816+
direction_types = [e.value for e in LayoutDirectionEnum]
808817
if direction not in direction_types:
809-
direction = NODE_LAYOUT_HORIZONTAL
818+
direction = LayoutDirectionEnum.HORIZONTAL.value
819+
self._model.layout_direction = direction
820+
for node in self.all_nodes():
821+
node.set_layout_direction(direction)
810822
self._viewer.set_layout_direction(direction)
811823

812824
def fit_to_selection(self):
@@ -945,6 +957,9 @@ def format_color(clr):
945957
if pos:
946958
node.model.pos = [float(pos[0]), float(pos[1])]
947959

960+
# initial node direction layout.
961+
node.model.layout_direction = self.layout_direction()
962+
948963
node.update()
949964

950965
if push_undo:
@@ -987,6 +1002,11 @@ def add_node(self, node, pos=None, selected=True, push_undo=True):
9871002
node.NODE_NAME = self.get_unique_name(node.NODE_NAME)
9881003
node.model._graph_model = self.model
9891004
node.model.name = node.NODE_NAME
1005+
1006+
# initial node direction layout.
1007+
node.model.layout_direction = self.layout_direction()
1008+
1009+
# update method must be called before it's been added to the viewer.
9901010
node.update()
9911011

9921012
if push_undo:
@@ -1697,7 +1717,7 @@ def auto_layout_nodes(self, nodes=None, down_stream=True, start_nodes=None):
16971717

16981718
node_layout_direction = self._viewer.get_layout_direction()
16991719

1700-
if node_layout_direction is NODE_LAYOUT_HORIZONTAL:
1720+
if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value:
17011721
current_x = 0
17021722
node_height = 120
17031723
for rank in sorted(range(len(rank_map)), reverse=not down_stream):
@@ -1712,7 +1732,7 @@ def auto_layout_nodes(self, nodes=None, down_stream=True, start_nodes=None):
17121732
current_y += dy * 0.5 + 10
17131733

17141734
current_x += max_width * 0.5 + 100
1715-
elif node_layout_direction is NODE_LAYOUT_VERTICAL:
1735+
elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value:
17161736
current_y = 0
17171737
node_width = 250
17181738
for rank in sorted(range(len(rank_map)), reverse=not down_stream):
@@ -1992,9 +2012,9 @@ def _build_port_nodes(self):
19922012
input_nodes[port.name()] = input_node
19932013
self.add_node(input_node, selected=False, push_undo=False)
19942014
x, y = input_node.pos()
1995-
if node_layout_direction is NODE_LAYOUT_HORIZONTAL:
2015+
if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value:
19962016
x -= 100
1997-
elif node_layout_direction is NODE_LAYOUT_VERTICAL:
2017+
elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value:
19982018
y -= 100
19992019
input_node.set_property('pos', [x, y], push_undo=False)
20002020

@@ -2010,9 +2030,9 @@ def _build_port_nodes(self):
20102030
output_nodes[port.name()] = output_node
20112031
self.add_node(output_node, selected=False, push_undo=False)
20122032
x, y = output_node.pos()
2013-
if node_layout_direction is NODE_LAYOUT_HORIZONTAL:
2033+
if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value:
20142034
x += 100
2015-
elif node_layout_direction is NODE_LAYOUT_VERTICAL:
2035+
elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value:
20162036
y += 100
20172037
output_node.set_property('pos', [x, y], push_undo=False)
20182038

NodeGraphQt/base/graph_actions.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ def build_context_menu(graph):
88
graph (NodeGraphQt.NodeGraph): node graph controller.
99
"""
1010
from Qt import QtGui, QtCore
11-
graph_menu = graph.get_context_menu('graph')
11+
context_menu = graph.get_context_menu('graph')
1212

1313
# "File" menu.
1414
# --------------------------------------------------------------------------
15-
file_menu = graph_menu.add_menu('&File')
15+
file_menu = context_menu.add_menu('&File')
1616

1717
file_menu.add_command('Open...', _open_session, QtGui.QKeySequence.Open)
1818
file_menu.add_command('Import...', _import_session)
@@ -22,7 +22,7 @@ def build_context_menu(graph):
2222

2323
# "Edit" menu.
2424
# --------------------------------------------------------------------------
25-
edit_menu = graph_menu.add_menu('&Edit')
25+
edit_menu = context_menu.add_menu('&Edit')
2626

2727
edit_menu.add_separator()
2828
edit_menu.add_command('Clear Undo History', _clear_undo)
@@ -49,20 +49,24 @@ def build_context_menu(graph):
4949
edit_menu.add_command('Zoom Out', _zoom_out, '-')
5050
edit_menu.add_command('Reset Zoom', _reset_zoom, 'H')
5151

52-
edit_menu.add_separator()
52+
context_menu.add_separator()
5353

54-
# "Grid Mode" submenu.
54+
# "Node" menu.
5555
# --------------------------------------------------------------------------
56-
bg_menu = edit_menu.add_menu('&Grid Mode')
56+
graph_menu = context_menu.add_menu('&Graph')
57+
58+
bg_menu = graph_menu.add_menu('&Background')
5759
bg_menu.add_command('None', _bg_grid_none)
5860
bg_menu.add_command('Lines', _bg_grid_lines)
5961
bg_menu.add_command('Dots', _bg_grid_dots)
6062

61-
edit_menu.add_separator()
63+
layout_menu = graph_menu.add_menu('&Layout')
64+
layout_menu.add_command('Horizontal', _layout_h_mode)
65+
layout_menu.add_command('Vertical', _layout_v_mode)
6266

6367
# "Node" menu.
6468
# --------------------------------------------------------------------------
65-
node_menu = graph_menu.add_menu('&Nodes')
69+
node_menu = context_menu.add_menu('&Nodes')
6670
node_menu.add_command('Node Search', _toggle_node_search, 'Tab')
6771
node_menu.add_separator()
6872
node_menu.add_command(
@@ -76,7 +80,7 @@ def build_context_menu(graph):
7680

7781
# "Pipe" menu.
7882
# --------------------------------------------------------------------------
79-
pipe_menu = graph_menu.add_menu('&Pipes')
83+
pipe_menu = context_menu.add_menu('&Pipes')
8084
pipe_menu.add_command('Curved', _curved_pipe)
8185
pipe_menu.add_command('Straight', _straight_pipe)
8286
pipe_menu.add_command('Angle', _angle_pipe)
@@ -109,6 +113,20 @@ def _reset_zoom(graph):
109113
graph.reset_zoom()
110114

111115

116+
def _layout_h_mode(graph):
117+
"""
118+
Set node graph layout direction to horizontal.
119+
"""
120+
graph.set_layout_direction(0)
121+
122+
123+
def _layout_v_mode(graph):
124+
"""
125+
Set node graph layout direction to vertical.
126+
"""
127+
graph.set_layout_direction(1)
128+
129+
112130
def _open_session(graph):
113131
"""
114132
Prompts a file open dialog to load a session.

NodeGraphQt/base/model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections import defaultdict
44

55
from NodeGraphQt.constants import (
6+
LayoutDirectionEnum,
67
NODE_PROP,
78
NODE_PROP_QLABEL,
89
NODE_PROP_QLINEEDIT,
@@ -73,6 +74,7 @@ def __init__(self):
7374
self.width = 100.0
7475
self.height = 80.0
7576
self.pos = [0.0, 0.0]
77+
self.layout_direction = LayoutDirectionEnum.HORIZONTAL.value
7678

7779
# BaseNode attrs.
7880
self.inputs = {}
@@ -107,6 +109,7 @@ def __init__(self):
107109
'width': NODE_PROP,
108110
'height': NODE_PROP,
109111
'pos': NODE_PROP,
112+
'layout_direction': NODE_PROP,
110113
'inputs': NODE_PROP,
111114
'outputs': NODE_PROP,
112115
}
@@ -230,6 +233,7 @@ def to_dict(self):
230233
'width': 0.0,
231234
'height: 0.0,
232235
'pos': (0.0, 0.0),
236+
'layout_direction': 0,
233237
'custom': {},
234238
'inputs': {
235239
<port_name>: {<node_id>: [<port_name>, <port_name>]}
@@ -317,6 +321,7 @@ def __init__(self):
317321
self.session = ''
318322
self.acyclic = True
319323
self.pipe_collision = False
324+
self.layout_direction = LayoutDirectionEnum.HORIZONTAL.value
320325

321326
def common_properties(self):
322327
"""

NodeGraphQt/base/node.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
#!/usr/bin/python
22
from NodeGraphQt.base.commands import PropertyChangedCmd
33
from NodeGraphQt.base.model import NodeModel
4-
from NodeGraphQt.constants import (NODE_PROP,
5-
NODE_LAYOUT_DIRECTION,
6-
NODE_LAYOUT_VERTICAL,
7-
NODE_LAYOUT_HORIZONTAL)
4+
from NodeGraphQt.constants import NODE_PROP
85

96

107
class _ClassProperty(object):
@@ -26,21 +23,16 @@ class NodeObject(object):
2623
:class:`NodeGraphQt.BackdropNode`
2724
2825
Args:
29-
qgraphics_views (dict): Dictionary with the node layout type as the key
30-
and a custom graphics item subclassed from the ``AbstractNodeItem``
31-
as the value.
26+
qgraphics_item (AbstractNodeItem): QGraphicsItem item used for drawing.
3227
3328
.. code-block:: python
3429
3530
# snippet taken from the NodeGraphQt.BaseNode class.
3631
3732
class BaseNode(NodeObject):
3833
39-
def __init__(self, qgraphics_views=None):
40-
qgraphics_views = qgraphics_views or {
41-
NodeGraphQt.constants.NODE_LAYOUT_HORIZONTAL: NodeItem,
42-
NodeGraphQt.constants.NODE_LAYOUT_VERTICAL: NodeItemVertical
43-
}
34+
def __init__(self, qgraphics_item=None):
35+
qgraphics_item = qgraphics_item or NodeItem
4436
super(BaseNode, self).__init__(qgraphics_views)
4537
4638
"""
@@ -51,29 +43,27 @@ def __init__(self, qgraphics_views=None):
5143
# Base node name.
5244
NODE_NAME = None
5345

54-
def __init__(self, qgraphics_views=None):
46+
def __init__(self, qgraphics_item=None):
47+
"""
48+
Args:
49+
qgraphics_item (AbstractNodeItem): QGraphicsItem used for drawing.
50+
"""
5551
self._graph = None
5652
self._model = NodeModel()
5753
self._model.type_ = self.type_
5854
self._model.name = self.NODE_NAME
5955

60-
_NodeItem = None
61-
if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL:
62-
_NodeItem = qgraphics_views.get(NODE_LAYOUT_VERTICAL)
63-
elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL:
64-
_NodeItem = qgraphics_views.get(NODE_LAYOUT_HORIZONTAL)
65-
56+
_NodeItem = qgraphics_item
6657
if _NodeItem is None:
67-
raise ValueError(
68-
'qgraphics item for the {} node layout can\'t be None!'.format({
69-
NODE_LAYOUT_VERTICAL: 'vertical',
70-
NODE_LAYOUT_HORIZONTAL: 'horizontal'
71-
}[NODE_LAYOUT_DIRECTION]))
58+
raise RuntimeError(
59+
'No qgraphics item specified for the node object!'
60+
)
7261

7362
self._view = _NodeItem()
7463
self._view.type_ = self.type_
7564
self._view.name = self.model.name
7665
self._view.id = self._model.id
66+
self._view.layout_direction = self._model.layout_direction
7767

7868
def __repr__(self):
7969
return '<{}("{}") object at {}>'.format(
@@ -217,6 +207,7 @@ def serialize(self):
217207
'width': 0.0,
218208
'height: 0.0,
219209
'pos': (0.0, 0.0),
210+
'layout_direction': 0,
220211
'custom': {},
221212
}
222213
}
@@ -469,3 +460,25 @@ def pos(self):
469460
self.model.pos = self.view.xy_pos
470461

471462
return self.model.pos
463+
464+
def layout_direction(self):
465+
"""
466+
Returns the current node layout direction.
467+
468+
Returns:
469+
int: node layout direction.
470+
"""
471+
return self.model.layout_direction
472+
473+
def set_layout_direction(self, value=0):
474+
"""
475+
Sets the node layout direction to either horizontal or vertical.
476+
477+
Note:
478+
This function does not register to the undo stack.
479+
480+
Args:
481+
value (int): layout direction mode.
482+
"""
483+
self.model.layout_direction = value
484+
self.view.layout_direction = value

NodeGraphQt/constants.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,10 @@ class LayoutDirectionEnum(Enum):
5858
Node graph nodes layout direction:
5959
:py:mod:`NodeGraphQt.constants.ViewerLayoutEnum`
6060
"""
61-
#: layout nodes top to bottom.
62-
VERTICAL = 0
6361
#: layout nodes left to right.
64-
HORIZONTAL = 1
65-
66-
67-
#: Variable for setting the node layout direction.
68-
# NODE_LAYOUT_DIRECTION = LayoutDirectionEnum.VERTICAL.value
69-
NODE_LAYOUT_DIRECTION = LayoutDirectionEnum.HORIZONTAL.value
62+
HORIZONTAL = 0
63+
#: layout nodes top to bottom.
64+
VERTICAL = 1
7065

7166

7267
# =================================== VIEWER ===================================

0 commit comments

Comments
 (0)