scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +130 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +268 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +440 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +768 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +981 -0
  17. pyworkflow/gui/form.py +2727 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +571 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +193 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +240 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +519 -0
  35. pyworkflow/gui/project/viewprotocols.py +2141 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +774 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +226 -0
  43. pyworkflow/mapper/sqlite.py +1583 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/object.py +1 -0
  46. pyworkflow/plugin.py +4 -4
  47. pyworkflow/project/__init__.py +31 -0
  48. pyworkflow/project/config.py +454 -0
  49. pyworkflow/project/manager.py +180 -0
  50. pyworkflow/project/project.py +2095 -0
  51. pyworkflow/project/usage.py +165 -0
  52. pyworkflow/protocol/__init__.py +38 -0
  53. pyworkflow/protocol/bibtex.py +48 -0
  54. pyworkflow/protocol/constants.py +87 -0
  55. pyworkflow/protocol/executor.py +515 -0
  56. pyworkflow/protocol/hosts.py +318 -0
  57. pyworkflow/protocol/launch.py +277 -0
  58. pyworkflow/protocol/package.py +42 -0
  59. pyworkflow/protocol/params.py +781 -0
  60. pyworkflow/protocol/protocol.py +2712 -0
  61. pyworkflow/resources/protlabels.xcf +0 -0
  62. pyworkflow/resources/sprites.png +0 -0
  63. pyworkflow/resources/sprites.xcf +0 -0
  64. pyworkflow/template.py +1 -1
  65. pyworkflow/tests/__init__.py +29 -0
  66. pyworkflow/tests/test_utils.py +25 -0
  67. pyworkflow/tests/tests.py +342 -0
  68. pyworkflow/utils/__init__.py +38 -0
  69. pyworkflow/utils/dataset.py +414 -0
  70. pyworkflow/utils/echo.py +104 -0
  71. pyworkflow/utils/graph.py +169 -0
  72. pyworkflow/utils/log.py +293 -0
  73. pyworkflow/utils/path.py +528 -0
  74. pyworkflow/utils/process.py +154 -0
  75. pyworkflow/utils/profiler.py +92 -0
  76. pyworkflow/utils/progressbar.py +154 -0
  77. pyworkflow/utils/properties.py +618 -0
  78. pyworkflow/utils/reflection.py +129 -0
  79. pyworkflow/utils/utils.py +880 -0
  80. pyworkflow/utils/which.py +229 -0
  81. pyworkflow/webservices/__init__.py +8 -0
  82. pyworkflow/webservices/config.py +8 -0
  83. pyworkflow/webservices/notifier.py +152 -0
  84. pyworkflow/webservices/repository.py +59 -0
  85. pyworkflow/webservices/workflowhub.py +86 -0
  86. pyworkflowtests/tests/__init__.py +0 -0
  87. pyworkflowtests/tests/test_canvas.py +72 -0
  88. pyworkflowtests/tests/test_domain.py +45 -0
  89. pyworkflowtests/tests/test_logs.py +74 -0
  90. pyworkflowtests/tests/test_mappers.py +392 -0
  91. pyworkflowtests/tests/test_object.py +507 -0
  92. pyworkflowtests/tests/test_project.py +42 -0
  93. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  94. pyworkflowtests/tests/test_protocol_export.py +78 -0
  95. pyworkflowtests/tests/test_protocol_output.py +158 -0
  96. pyworkflowtests/tests/test_streaming.py +47 -0
  97. pyworkflowtests/tests/test_utils.py +210 -0
  98. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +2 -2
  99. scipion_pyworkflow-3.11.2.dist-info/RECORD +162 -0
  100. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  101. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
  102. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
  103. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
  104. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,247 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
4
+ # *
5
+ # * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
6
+ # *
7
+ # * This program is free software; you can redistribute it and/or modify
8
+ # * it under the terms of the GNU General Public License as published by
9
+ # * the Free Software Foundation; either version 3 of the License, or
10
+ # * (at your option) any later version.
11
+ # *
12
+ # * This program is distributed in the hope that it will be useful,
13
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # * GNU General Public License for more details.
16
+ # *
17
+ # * You should have received a copy of the GNU General Public License
18
+ # * along with this program; if not, write to the Free Software
19
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20
+ # * 02111-1307 USA
21
+ # *
22
+ # * All comments concerning this program package may be sent to the
23
+ # * e-mail address 'scipion@cnb.csic.es'
24
+ # *
25
+ # **************************************************************************
26
+ """
27
+ This module implements a simple algorithm to display a graph(mainly a tree)
28
+ level by level, using only Tkinter.
29
+ """
30
+
31
+
32
+ # TODO: all LevelTree code is DEPRECATED...remove it after cleaning
33
+ # and include all code from graph_layout
34
+
35
+
36
+ class LevelTree(object):
37
+ """ Class to render the Graph in a Canvas. """
38
+
39
+ def __init__(self, graph):
40
+ self.DY = 65
41
+ self.DX = 15
42
+ self.FONT = "sans-serif"
43
+ self.FONTSIZE = 9
44
+ self.graph = graph
45
+ self.canvas = None
46
+
47
+ def setCanvas(self, canvas):
48
+ self.canvas = canvas
49
+
50
+ def paint(self, createNode=None, createEdge=None, maxLevel=9999, usePositions=False):
51
+ """ Paint the Graph, nodes will be positioned by levels.
52
+ Params:
53
+ canvas: the canvas object to paint the graph.
54
+ createNode: function to build the Item that represents a Node.
55
+ the Item created should have the following methods:
56
+ getDimensions: return the width and height
57
+ moveTo: change the position of the Item
58
+ createEdge: function to build an Edge connection two Nodes
59
+ usePositions: if this is True, use the nodes positions without
60
+ recomputing them.
61
+
62
+ If createNode and createEdge are None, the default ones will be used,
63
+ that requires the setCanvas method had to be called first.
64
+ """
65
+ self.createNode = createNode or self._defaultCreateNode
66
+ self.createEdge = createEdge or self._defaultCreateEdge
67
+ self.maxLevel = maxLevel
68
+ rootNode = self.graph.getRoot()
69
+
70
+ if usePositions:
71
+ self._paintNodeWithPosition(rootNode)
72
+ self._paintEdges(rootNode)
73
+ else:
74
+ self._setLevel(rootNode, 0, None)
75
+ self._paintNodeWithChilds(rootNode, 1)
76
+ m = 9999
77
+ for left, right in rootNode.hLimits:
78
+ m = min(m, left)
79
+ self._createEdges(rootNode, -m + self.DY)
80
+
81
+ def _setLevel(self, node, level, parent):
82
+ """ Set the level of the nodes. """
83
+ node.level = level
84
+ node.parent = parent
85
+ nextLevel = level + 1
86
+ if nextLevel > self.maxLevel:
87
+ return
88
+ for child in node.getChildren():
89
+ if nextLevel > getattr(child, 'level', 0):
90
+ self._setLevel(child, nextLevel, node)
91
+
92
+ def _paintNodeWithChilds(self, node, level):
93
+ y = level * self.DY
94
+
95
+ self._paintNode(node, y)
96
+
97
+ if level > self.maxLevel:
98
+ return
99
+
100
+ childs = [c for c in node.getChildren() if c.parent is node]
101
+ n = len(childs)
102
+
103
+ if n > 0:
104
+ # width = (xmax - xmin) / n
105
+ for c in childs:
106
+ self._paintNodeWithChilds(c, level + 1)
107
+
108
+ if n > 1:
109
+ offset = 0
110
+ for i in range(n - 1):
111
+ sep = self._getChildsSeparation(childs[i], childs[i + 1])
112
+ offset += sep
113
+ c = childs[i + 1]
114
+ c.offset = offset
115
+
116
+ total = childs[0].half + offset + childs[-1].half
117
+ half = total / 2
118
+ for c in childs:
119
+ c.offset -= half - childs[0].half
120
+
121
+ else:
122
+ childs[0].offset = 0
123
+ self._getHLimits(node)
124
+
125
+ def _defaultCreateNode(self, canvas, node, y):
126
+ """ If not createNode is specified, this one will be used
127
+ by default.
128
+ """
129
+ if canvas is None:
130
+ raise Exception("method setCanvas should be called before using _defaultCreateNode")
131
+ nodeText = node.getLabel()
132
+ textColor = 'black'
133
+
134
+ return canvas.createTextbox(nodeText, 100, y, bgColor='light blue', textColor=textColor, margin=0)
135
+
136
+ def _defaultCreateEdge(self, srcItem, dstItem):
137
+ if self.canvas is None:
138
+ raise Exception("method setCanvas should be called before using _defaultCreateEdge")
139
+ self.canvas.createEdge(srcItem, dstItem)
140
+
141
+ def _paintNode(self, node, y):
142
+ """ Paint a node of the graph.
143
+ Params:
144
+ canvas: the canvas in which to paint.
145
+ node: the node of the graph to be painted.
146
+ y: level in the tree where to paint.
147
+ Returns:
148
+ the create item in the canvas.
149
+ """
150
+ item = self.createNode(self.canvas, node, y)
151
+ node.width, node.height = item.getDimensions()
152
+ node.half = node.width / 2
153
+ node.hLimits = [[-node.half, node.half]]
154
+ node.y = item.y
155
+ node.offset = 0
156
+ # Create link from both sides to reach
157
+ node.item = item
158
+ item.node = node
159
+
160
+ return item
161
+
162
+ def _printHLimits(self, node, msg):
163
+ print("\n=====%s========" % msg)
164
+ print(" dd: %s" % node.t.text.replace('\n', '_'))
165
+ print(" offset: %d, width: %d" % (node.offset, node.width))
166
+ print(" hlimits:")
167
+ for l, r in node.hLimits:
168
+ print(" [%d, %d]" % (l, r))
169
+
170
+ def _getHLimits(self, node):
171
+ """
172
+ This function will traverse the tree
173
+ from node to build the left and right profiles(hLimits)
174
+ for each level of the tree
175
+ """
176
+ node.hLimits = [[-node.half, node.half]]
177
+ childs = [c for c in node.getChildren() if c.parent is node]
178
+ for child in childs:
179
+ count = 1
180
+ if not hasattr(child, 'hLimits'):
181
+ print("node %s has no hLimits" % child.label)
182
+ raise Exception()
183
+
184
+ for l, r in child.hLimits:
185
+ l += child.offset
186
+ r += child.offset
187
+ if count < len(node.hLimits):
188
+ if l < node.hLimits[count][0]:
189
+ node.hLimits[count][0] = l
190
+ if r > node.hLimits[count][1]:
191
+ node.hLimits[count][1] = r
192
+ else:
193
+ node.hLimits.append([l, r])
194
+ count += 1
195
+
196
+ def _getChildsSeparation(self, child1, child2):
197
+ """ Calculate separation between siblings
198
+ at each height level. """
199
+ sep = 0
200
+ hL1 = child1.hLimits
201
+ hL2 = child2.hLimits
202
+ n1 = len(hL1)
203
+ n2 = len(hL2)
204
+ h = min(n1, n2)
205
+
206
+ for i in range(h):
207
+ right = hL1[i][1]
208
+ left = hL2[i][0]
209
+ if left + sep < right:
210
+ sep = right - left
211
+
212
+ return sep + self.DX
213
+
214
+ def _createEdges(self, node, x):
215
+ """ Adjust the position of the nodes
216
+ and create the edges between them.
217
+ """
218
+ nx = x + node.offset
219
+ node.item.moveTo(nx, node.y)
220
+
221
+ if node.level == self.maxLevel:
222
+ return
223
+
224
+ for c in node.getChildren():
225
+ if c.parent is node:
226
+ self._createEdges(c, nx)
227
+ self.createEdge(node.item, c.item)
228
+
229
+ def _paintNodeWithPosition(self, node):
230
+ """ Paint nodes using its position. """
231
+ self._paintNode(node, None)
232
+
233
+ for child in node.getChildren():
234
+ # parent = None for nodes that have been not traversed
235
+ parent = getattr(child, 'parent', None)
236
+ if parent is None:
237
+ child.parent = node
238
+ self._paintNodeWithPosition(child)
239
+
240
+ def _paintEdges(self, node):
241
+ """ Paint only the edges between nodes, assuming they are
242
+ already well positioned.
243
+ """
244
+ for child in node.getChildren():
245
+ if child.parent is node:
246
+ self._paintEdges(child)
247
+ self.createEdge(node.item, child.item)
@@ -0,0 +1,271 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
4
+ # *
5
+ # * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
6
+ # *
7
+ # * This program is free software; you can redistribute it and/or modify
8
+ # * it under the terms of the GNU General Public License as published by
9
+ # * the Free Software Foundation; either version 3 of the License, or
10
+ # * (at your option) any later version.
11
+ # *
12
+ # * This program is distributed in the hope that it will be useful,
13
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # * GNU General Public License for more details.
16
+ # *
17
+ # * You should have received a copy of the GNU General Public License
18
+ # * along with this program; if not, write to the Free Software
19
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20
+ # * 02111-1307 USA
21
+ # *
22
+ # * All comments concerning this program package may be sent to the
23
+ # * e-mail address 'scipion@cnb.csic.es'
24
+ # *
25
+ # **************************************************************************
26
+ import logging
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ from pyworkflow import Config, SCIPION_DEFAULT_FONT_SIZE
31
+
32
+
33
+
34
+ class GraphLayout(object):
35
+ """ Base class for all algorithm that implement
36
+ functions to organize a graph in a plane.
37
+ """
38
+ def __init__(self):
39
+ super().__init__()
40
+ self.DY = 65
41
+ self.DX = 15
42
+ self._fontScaleFactor = None
43
+
44
+ def getY(self):
45
+ """
46
+ :return: Y distance affected by the font size
47
+
48
+ """
49
+
50
+ return self.DY*self.getFontScaleFactor()
51
+
52
+ def getFontScaleFactor(self):
53
+ """
54
+ :return: The scale factor between default font size 10, and current one
55
+
56
+ """
57
+ if self._fontScaleFactor is None:
58
+
59
+ self._fontScaleFactor = Config.SCIPION_FONT_SIZE/SCIPION_DEFAULT_FONT_SIZE
60
+
61
+ return self._fontScaleFactor
62
+
63
+ def draw(self, graph, **kwargs):
64
+ """ Setup the nodes position in the plane. """
65
+ pass
66
+
67
+
68
+ class LevelTreeLayout(GraphLayout):
69
+ """ Organize the nodes of the graph by levels.
70
+ It will recursively organize children and then
71
+ fit the sibling trees. """
72
+
73
+ def __init__(self, partial=False):
74
+ GraphLayout.__init__(self)
75
+ self.maxLevel = 9999
76
+ self.partial = partial
77
+
78
+ def draw(self, graph, **kwargs):
79
+ """
80
+ Organize nodes of the graph in the plane.
81
+ Nodes should have x, y, width and height attributes.
82
+ x and y will be modified.
83
+ """
84
+ rootNode = graph.getRoot()
85
+
86
+ # Setup empty layout for each node
87
+ for node in graph.getNodes():
88
+ node._layout = {}
89
+
90
+ visitDict = dict()
91
+ # Do some level initialization on each node
92
+ self._setLayoutLevel(rootNode, 1, visitDict)
93
+ self._computeNodeOffsets(rootNode, 1)
94
+ # Compute extreme left limit
95
+ m = 9999
96
+ for left, _ in rootNode._layout['hLimits']:
97
+ m = min(m, left)
98
+
99
+ self._applyNodeOffsets(rootNode, -m + self.getY())
100
+
101
+ # Clean temporary _layout attributes
102
+ for node in graph.getNodes():
103
+ del node._layout
104
+
105
+ def _isNewNode(self, node):
106
+ return node.x == 0 or node.y == 0 or node.isRoot()
107
+
108
+ def _setLayoutLevel(self, node, level, parent, ancestors=[]):
109
+ """ Iterate over all nodes and set _layout dict.
110
+ Also set the node level, which is defined
111
+ as the max level of a parent + 1
112
+ """
113
+ if level > self.maxLevel:
114
+ return
115
+
116
+ layout = node._layout
117
+
118
+ if level > layout.get('level', 0):
119
+ # Calculate the y-position depending on the level
120
+ # and the delta-Y (DY)
121
+ if not self.partial or self._isNewNode(node):
122
+ node.y = level * self.getY()
123
+ layout['level'] = level
124
+ layout['parent'] = parent
125
+ if hasattr(node, 'width'):
126
+ half = node.width / 2
127
+ else:
128
+ half = 50
129
+ layout['half'] = half
130
+ layout['hLimits'] = [[-half, half]]
131
+ layout['offset'] = 0
132
+
133
+ if self.__isNodeExpanded(node):
134
+ ancestors.append(node.getName())
135
+ for child in node.getChildren():
136
+ if child.getName() in ancestors:
137
+ logger.warning("WARNING: There might be a cyclic redundancy error in this protocol: %s (%s)" %(child.getLabel(),
138
+ child.getName()))
139
+ if Config.debugOn():
140
+ print("%s: Setting layout for child %s" % ("-" * level, child), flush=True)
141
+ self._setLayoutLevel(child, level+1, node, ancestors.copy())
142
+
143
+ def __isNodeExpanded(self, node):
144
+ """ Check if the status of the node is expanded or collapsed. """
145
+ return getattr(node, 'expanded', True)
146
+
147
+ def __setNodeOffset(self, node, offset):
148
+ node._layout['offset'] = offset
149
+
150
+ def __getNodeHalf(self, node):
151
+ return node._layout['half']
152
+
153
+ def __getNodeChilds(self, node):
154
+ """ Return the node's childs that have been
155
+ visited by this node first (its 'parent')
156
+ """
157
+ if self.__isNodeExpanded(node):
158
+ return [c for c in node.getChildren() if c._layout['parent'] is node]
159
+ else:
160
+ return [] # treat collapsed nodes as if they have no childs
161
+
162
+ def _computeNodeOffsets(self, node, level):
163
+ """ Position a parent node and its childs.
164
+ Only this sub-tree will be considered at this point.
165
+ Then it will be adjusted with node siblings.
166
+ """
167
+ if level > self.maxLevel:
168
+ return
169
+
170
+ childs = self.__getNodeChilds(node)
171
+ n = len(childs)
172
+
173
+ if n > 0:
174
+ for c in childs:
175
+ self._computeNodeOffsets(c, level + 1)
176
+
177
+ if n > 1:
178
+ offset = 0
179
+ # Keep right limits to compute the separation between siblings
180
+ # some times it not enough to compare with the left sibling
181
+ # for some child levels of the node
182
+ rightLimits = [r for l, r in childs[0]._layout['hLimits']]
183
+
184
+ for i in range(n-1):
185
+ sep = self._getChildsSeparation(childs[i], childs[i+1], rightLimits)
186
+ offset += sep
187
+ c = childs[i+1]
188
+ self.__setNodeOffset(c, offset)
189
+
190
+ half0 = self.__getNodeHalf(childs[0])
191
+ total = half0 + offset + self.__getNodeHalf(childs[-1])
192
+ half = total / 2
193
+ for c in childs:
194
+ self.__setNodeOffset(c, c._layout['offset'] - half + half0)
195
+ else:
196
+ self.__setNodeOffset(childs[0], 0)
197
+
198
+ self._computeHLimits(node)
199
+
200
+ def _computeHLimits(self, node):
201
+ """ This function will traverse the tree
202
+ from node to build the left and right profiles(hLimits)
203
+ for each level of the tree
204
+ """
205
+ layout = node._layout
206
+ hLimits = layout['hLimits']
207
+
208
+ childs = self.__getNodeChilds(node)
209
+
210
+ for child in childs:
211
+ count = 1
212
+ layout = child._layout
213
+ for l, r in layout['hLimits']:
214
+ l += layout['offset']
215
+ r += layout['offset']
216
+
217
+ if count < len(hLimits):
218
+ if l < hLimits[count][0]:
219
+ hLimits[count][0] = l
220
+ if r > hLimits[count][1]:
221
+ hLimits[count][1] = r
222
+ else:
223
+ hLimits.append([l, r])
224
+ count += 1
225
+
226
+ def _getChildsSeparation(self, child1, child2, rightLimits):
227
+ """Calculate separation between siblings
228
+ at each height level"""
229
+ sep = 0
230
+ hL2 = child2._layout['hLimits']
231
+ n1 = len(rightLimits)
232
+ n2 = len(hL2)
233
+ h = min(n1, n2)
234
+
235
+ for i in range(h):
236
+ right = rightLimits[i]
237
+ left = hL2[i][0]
238
+ if left + sep < right:
239
+ sep = right - left
240
+ rightLimits[i] = hL2[i][1]
241
+
242
+ if n1 > n2:
243
+ # If there are more levels in the rightLimits
244
+ # updated the last ones like if they belong
245
+ # to next sibling is is now (sep + self.DX) away
246
+ for i in range(h, n1):
247
+ rightLimits[i] -= sep + self.DX
248
+ else:
249
+ # If the current right sibling has more levels
250
+ # just add them to the current rightLimits
251
+ for i in range(h, n2):
252
+ rightLimits.append(hL2[i][1])
253
+
254
+ return sep + self.DX
255
+
256
+ def _applyNodeOffsets(self, node, x):
257
+ """ Adjust the x-position of the nodes by applying the offsets.
258
+ """
259
+ if node._layout['level'] == self.maxLevel:
260
+ return
261
+
262
+ layout = node._layout
263
+
264
+ if not self.partial or self._isNewNode(node):
265
+ node.x = x + layout['offset']
266
+
267
+ childs = self.__getNodeChilds(node)
268
+
269
+ for child in childs:
270
+ self._applyNodeOffsets(child, node.x)
271
+