iplotx 0.3.1__py3-none-any.whl → 0.4.0__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.
@@ -0,0 +1,121 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ Any,
5
+ )
6
+ from collections.abc import Hashable
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ GraphType,
12
+ LayoutType,
13
+ )
14
+ from ...heuristics import (
15
+ normalise_layout,
16
+ )
17
+ from ...typing import (
18
+ NetworkDataProvider,
19
+ NetworkData,
20
+ )
21
+ from ....utils.internal import (
22
+ _make_layout_columns,
23
+ )
24
+
25
+
26
+ class SimpleDataProvider(NetworkDataProvider):
27
+ def __call__(
28
+ self,
29
+ layout: Optional[LayoutType] = None,
30
+ vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
31
+ edge_labels: Optional[Sequence[str] | dict[str]] = None,
32
+ ) -> NetworkData:
33
+ """Create network data object for iplotx from a simple Python object."""
34
+ network = self.network
35
+ directed = self.is_directed()
36
+
37
+ # Recast vertex_labels=False as vertex_labels=None
38
+ if np.isscalar(vertex_labels) and (not vertex_labels):
39
+ vertex_labels = None
40
+
41
+ # Vertices are ordered integers, no gaps
42
+ for key in ["nodes", "vertices"]:
43
+ if key in network:
44
+ vertices = network[key]
45
+ break
46
+ else:
47
+ # Infer from edge adjacent vertices, singletons will be missed
48
+ vertices = set()
49
+ for edge in self.network.get("edges", []):
50
+ vertices.add(edge[0])
51
+ vertices.add(edge[1])
52
+ vertices = list(vertices)
53
+
54
+ # NOTE: This is underpowered, but it's ok for a simple educational provider
55
+ if isinstance(layout, pd.DataFrame):
56
+ vertex_df = layout.loc[vertices].copy()
57
+ elif isinstance(layout, dict):
58
+ vertex_df = pd.DataFrame(layout).T.loc[vertices]
59
+ else:
60
+ vertex_df = pd.DataFrame(
61
+ index=vertices,
62
+ data=layout,
63
+ )
64
+ ndim = vertex_df.shape[1]
65
+ vertex_df.columns = _make_layout_columns(ndim)
66
+
67
+ # Vertex labels
68
+ if vertex_labels is not None:
69
+ if np.isscalar(vertex_labels):
70
+ vertex_df["label"] = vertex_df.index.astype(str)
71
+ elif len(vertex_labels) != len(vertex_df):
72
+ raise ValueError(
73
+ "Vertex labels must be the same length as the number of vertices."
74
+ )
75
+ else:
76
+ vertex_df["label"] = vertex_labels
77
+
78
+ # Edges are a list of tuples, because of multiedges
79
+ tmp = []
80
+ for edge in network.get("edges", []):
81
+ row = {"_ipx_source": edge[0], "_ipx_target": edge[1]}
82
+ tmp.append(row)
83
+ if len(tmp):
84
+ edge_df = pd.DataFrame(tmp)
85
+ else:
86
+ edge_df = pd.DataFrame(columns=["_ipx_source", "_ipx_target"])
87
+ del tmp
88
+
89
+ network_data = {
90
+ "vertex_df": vertex_df,
91
+ "edge_df": edge_df,
92
+ "directed": directed,
93
+ "ndim": ndim,
94
+ }
95
+ return network_data
96
+
97
+ @staticmethod
98
+ def check_dependencies() -> bool:
99
+ """Check dependencies. Returns True since this provider has no dependencies."""
100
+ return True
101
+
102
+ @staticmethod
103
+ def graph_type():
104
+ return dict
105
+
106
+ def is_directed(self):
107
+ """Whether the network is directed."""
108
+ return self.network.get("directed", False)
109
+
110
+ def number_of_vertices(self):
111
+ """The number of vertices/nodes in the network."""
112
+ for key in ("nodes", "vertices"):
113
+ if key in self.network:
114
+ return len(self.network[key])
115
+
116
+ # Default to unique edge adjacent nodes (this will ignore singletons)
117
+ nodes = set()
118
+ for edge in self.network.get("edges", []):
119
+ nodes.add(edge[0])
120
+ nodes.add(edge[1])
121
+ return len(nodes)
@@ -45,3 +45,16 @@ class BiopythonDataProvider(TreeDataProvider):
45
45
  from Bio import Phylo
46
46
 
47
47
  return Phylo.BaseTree.Tree
48
+
49
+ def get_support(self):
50
+ """Get support/confidence values for all nodes."""
51
+ support_dict = {}
52
+ for node in self.preorder():
53
+ if hasattr(node, "confidences"):
54
+ support = node.confidences
55
+ elif hasattr(node, "confidence"):
56
+ support = node.confidence
57
+ else:
58
+ support = None
59
+ support_dict[node] = support
60
+ return support_dict
@@ -39,3 +39,10 @@ class Cogent3DataProvider(TreeDataProvider):
39
39
  from cogent3.core.tree import PhyloNode
40
40
 
41
41
  return PhyloNode
42
+
43
+ def get_support(self):
44
+ """Get support values for all nodes."""
45
+ support_dict = {}
46
+ for node in self.preorder():
47
+ support_dict[node] = node.params.get("support", None)
48
+ return support_dict
iplotx/ingest/typing.py CHANGED
@@ -40,9 +40,19 @@ class NetworkData(TypedDict):
40
40
  class NetworkDataProvider(Protocol):
41
41
  """Protocol for network data ingestion provider for iplotx."""
42
42
 
43
- def __call__(
43
+ def __init__(
44
44
  self,
45
45
  network: GraphType,
46
+ ) -> None:
47
+ """Initialise network data provider.
48
+
49
+ Parameters:
50
+ network: The network to ingest.
51
+ """
52
+ self.network = network
53
+
54
+ def __call__(
55
+ self,
46
56
  layout: Optional[LayoutType] = None,
47
57
  vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
48
58
  edge_labels: Optional[Sequence[str] | dict] = None,
@@ -60,6 +70,14 @@ class NetworkDataProvider(Protocol):
60
70
  """Return the graph type from this provider to check for instances."""
61
71
  raise NotImplementedError("Network data providers must implement this method.")
62
72
 
73
+ def is_directed(self):
74
+ """Check whether the network is directed."""
75
+ raise NotImplementedError("Network data providers must implement this method.")
76
+
77
+ def number_of_vertices(self):
78
+ """The number of vertices/nodes in the network."""
79
+ raise NotImplementedError("Network data providers must implement this method.")
80
+
63
81
 
64
82
  class TreeData(TypedDict):
65
83
  """Tree data structure for iplotx."""
@@ -190,23 +208,70 @@ class TreeDataProvider(Protocol):
190
208
  branch_length = self.get_branch_length(node)
191
209
  return branch_length if branch_length is not None else 1.0
192
210
 
211
+ def get_lca(
212
+ self,
213
+ nodes: Sequence[Hashable],
214
+ ) -> Hashable:
215
+ """Find the last common ancestor of a sequence of nodes.
216
+
217
+ Parameters:
218
+ nodes: The nodes to find a common ancestor for.
219
+
220
+ Returns:
221
+ The node that is the last (deepest) common ancestor of the nodes.
222
+
223
+ NOTE: individual providers may implement more efficient versions of
224
+ this function if desired.
225
+ """
226
+ provider = self.__class__
227
+
228
+ # Find leaves of the selected nodes
229
+ leaves = set()
230
+ for node in nodes:
231
+ # NOTE: get_leaves excludes the node itself...
232
+ if len(self.get_children(node)) == 0:
233
+ leaves.add(node)
234
+ else:
235
+ leaves |= set(provider(node).get_leaves())
236
+
237
+ # Look for nodes with the same set of leaves, starting from the bottom
238
+ # and stopping at the first (i.e. lowest) hit.
239
+ for node in self.postorder():
240
+ # NOTE: As above, get_leaves excludes the node itself
241
+ if len(self.get_children(node)) == 0:
242
+ leaves_node = {node}
243
+ else:
244
+ leaves_node = set(provider(node).get_leaves())
245
+ if leaves <= leaves_node:
246
+ root = node
247
+ break
248
+ else:
249
+ raise ValueError(f"Common ancestor not found for nodes: {nodes}")
250
+
251
+ return root
252
+
193
253
  def __call__(
194
254
  self,
195
255
  layout: str | LayoutType,
196
- orientation: Optional[str],
197
256
  layout_style: Optional[dict[str, int | float | str]] = None,
198
257
  directed: bool | str = False,
199
258
  vertex_labels: Optional[
200
259
  Sequence[str] | dict[Hashable, str] | pd.Series | bool
201
260
  ] = None,
202
261
  edge_labels: Optional[Sequence[str] | dict] = None,
203
- leaf_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
262
+ leaf_labels: Optional[
263
+ Sequence[str] | dict[Hashable, str] | pd.Series | bool
264
+ ] = None,
204
265
  ) -> TreeData:
205
- """Create tree data object for iplotx from ete4.core.tre.Tree classes."""
266
+ """Create tree data object for iplotx from ete4.core.tre.Tree classes.
267
+
268
+ NOTE: This function needs NOT be implemented by individual providers.
269
+ """
206
270
 
207
271
  if layout_style is None:
208
272
  layout_style = {}
209
273
 
274
+ orientation = layout_style.pop("orientation", None)
210
275
  if orientation is None:
211
276
  if layout == "horizontal":
212
277
  orientation = "right"
@@ -240,6 +305,15 @@ class TreeDataProvider(Protocol):
240
305
  else:
241
306
  tree_data["layout_coordinate_system"] = "cartesian"
242
307
 
308
+ # Add leaf_df
309
+ # NOTE: Sometimes (e.g. cogent3) the leaves convert into a pd.Index
310
+ # in a strange way, whereby their name disappears upon printing the
311
+ # index but is actually visible (and kept) when inspecting the
312
+ # individual elements (leaves). Seems ok functionally, though a little
313
+ # awkward visually during debugging.
314
+ tree_data["leaf_df"] = pd.DataFrame(index=self.get_leaves())
315
+ leaf_name_attrs = ("name",)
316
+
243
317
  # Add edge_df
244
318
  edge_data = {"_ipx_source": [], "_ipx_target": []}
245
319
  for node in self.preorder():
@@ -253,8 +327,26 @@ class TreeDataProvider(Protocol):
253
327
  edge_df = pd.DataFrame(edge_data)
254
328
  tree_data["edge_df"] = edge_df
255
329
 
256
- # Add leaf_df
257
- tree_data["leaf_df"] = pd.DataFrame(index=self.get_leaves())
330
+ # Add branch support
331
+ if hasattr(self, "get_support"):
332
+ support = self.get_support()
333
+
334
+ for key, value in support.items():
335
+ # Leaves never show support, it's not a branching point
336
+ if key in tree_data["leaf_df"].index:
337
+ support[key] = ""
338
+ elif value is None:
339
+ support[key] = ""
340
+ elif np.isscalar(value):
341
+ # Assume support is in percentage and round it to nearest integer.
342
+ support[key] = str(int(np.round(value, 0)))
343
+ else:
344
+ # Apparently multiple supports are accepted in some XML format
345
+ support[key] = "/".join(str(int(np.round(v, 0))) for v in value)
346
+
347
+ tree_data["vertex_df"]["support"] = pd.Series(support).loc[
348
+ tree_data["vertex_df"].index
349
+ ]
258
350
 
259
351
  # Add vertex labels
260
352
  if vertex_labels is None:
@@ -278,11 +370,18 @@ class TreeDataProvider(Protocol):
278
370
  if leaf_labels is None:
279
371
  leaf_labels = False
280
372
  if np.isscalar(leaf_labels) and leaf_labels:
281
- tree_data["leaf_labels"]["label"] = [
282
- # FIXME: this is likely broken
283
- x.name
284
- for x in tree_data["leaf_df"].index
285
- ]
373
+ leaf_labels = []
374
+ for leaf in tree_data["leaf_df"].index:
375
+ for name_attr in leaf_name_attrs:
376
+ if hasattr(leaf, name_attr):
377
+ label = getattr(leaf, name_attr)
378
+ break
379
+ else:
380
+ raise ValueError(
381
+ "Could not find leaf name attribute.",
382
+ )
383
+ leaf_labels.append(label)
384
+ tree_data["leaf_df"]["label"] = leaf_labels
286
385
  elif not np.isscalar(leaf_labels):
287
386
  # Leaves are already in the dataframe in a certain order, so sequences are allowed
288
387
  if isinstance(leaf_labels, (list, tuple, np.ndarray)):
iplotx/label.py CHANGED
@@ -17,6 +17,7 @@ from .style import (
17
17
  from .utils.matplotlib import (
18
18
  _stale_wrapper,
19
19
  _forwarder,
20
+ _get_label_width_height,
20
21
  )
21
22
 
22
23
 
@@ -108,12 +109,13 @@ class LabelCollection(mpl.artist.Artist):
108
109
  self._offsets[i][1],
109
110
  label,
110
111
  transform=transform,
112
+ rotation_mode="anchor",
111
113
  **stylei,
112
114
  )
113
115
  arts.append(art)
114
116
  self._labelartists = arts
115
117
  self._margins = np.array(margins)
116
- self._rotations = np.zeros(len(self._labels))
118
+ self._rotations = np.array([np.pi / 180 * art.get_rotation() for art in arts])
117
119
 
118
120
  def _update_offsets(self, dpi: float = 72.0) -> None:
119
121
  """Update offsets including margins."""
@@ -134,10 +136,11 @@ class LabelCollection(mpl.artist.Artist):
134
136
  transform = self.get_transform()
135
137
  trans = transform.transform
136
138
  trans_inv = transform.inverted().transform
137
- rotations = self.get_rotations()
138
- vrot = [np.cos(rotations), np.sin(rotations)]
139
139
 
140
+ # Add margins *before* applying the rotation
140
141
  margins_rot = np.empty_like(margins)
142
+ rotations = self.get_rotations()
143
+ vrot = [np.cos(rotations), np.sin(rotations)]
141
144
  margins_rot[:, 0] = margins[:, 0] * vrot[0] - margins[:, 1] * vrot[1]
142
145
  margins_rot[:, 1] = margins[:, 0] * vrot[1] + margins[:, 1] * vrot[0]
143
146
  offsets = trans_inv(trans(offsets) + margins_rot)
@@ -170,12 +173,12 @@ class LabelCollection(mpl.artist.Artist):
170
173
  rot_deg = 180.0 / np.pi * rotation
171
174
  # Force the font size to be upwards
172
175
  if ha == "auto":
173
- if -90 <= rot_deg < 90:
176
+ if -90 <= np.round(rot_deg, 3) < 90:
174
177
  art.set_horizontalalignment("left")
175
178
  else:
176
179
  art.set_horizontalalignment("right")
177
- rot_deg = ((rot_deg + 90) % 180) - 90
178
- art.set_rotation(rot_deg)
180
+ rot_deg_new = ((rot_deg + 90) % 180) - 90
181
+ art.set_rotation(rot_deg_new)
179
182
 
180
183
  def get_datalim(self, transData=None) -> mpl.transforms.Bbox:
181
184
  """Get the data limits of the labels."""
@@ -184,15 +187,42 @@ class LabelCollection(mpl.artist.Artist):
184
187
  return bbox
185
188
 
186
189
  def get_datalims_children(self, transData=None) -> Sequence[mpl.transforms.Bbox]:
187
- """Get the data limits of the children of this artist."""
190
+ """Get the data limits of the children of this artist."""
191
+ dpi = self.figure.dpi if self.figure is not None else 72.0
188
192
  if transData is None:
189
193
  transData = self.get_transform()
190
- trans_inv = transData.inverted().transform_bbox
194
+ trans = transData.transform
195
+ trans_inv = transData.inverted().transform
191
196
  bboxes = []
192
- for art in self._labelartists:
193
- bbox_fig = art.get_bbox_patch().get_extents()
194
- bbox_data = trans_inv(bbox_fig)
195
- bboxes.append(bbox_data)
197
+ for art, rot in zip(self._labelartists, self.get_rotations()):
198
+ # These are in figure points
199
+ textprops = ("text", "fontsize")
200
+ props = art.properties()
201
+ props = {key: props[key] for key in textprops}
202
+ props["dpi"] = dpi
203
+ props["hpadding"] = 12
204
+ props["vpadding"] = 12
205
+ width, height = _get_label_width_height(**props)
206
+
207
+ # Positions are in data coordinates
208
+ # and include the margins (it's the actual position of the
209
+ # text anchor)
210
+ pos_data = art.get_position()
211
+
212
+ # Four corners
213
+ dh = width / 2 * np.array([-np.sin(rot), np.cos(rot)])
214
+ dw = height * np.array([np.cos(rot), np.sin(rot)])
215
+ c1 = trans_inv(trans(pos_data) + dh)
216
+ c2 = trans_inv(trans(pos_data) - dh)
217
+ if art.get_horizontalalignment() == "right":
218
+ c3 = trans_inv(trans(pos_data) - dw + dh)
219
+ c4 = trans_inv(trans(pos_data) - dw - dh)
220
+ else:
221
+ c3 = trans_inv(trans(pos_data) + dw + dh)
222
+ c4 = trans_inv(trans(pos_data) + dw - dh)
223
+ bbox = mpl.transforms.Bbox.null()
224
+ bbox.update_from_data_xy([c1, c2, c3, c4], ignore=True)
225
+ bboxes.append(bbox)
196
226
  return bboxes
197
227
 
198
228
  @_stale_wrapper
iplotx/layout.py CHANGED
@@ -24,7 +24,7 @@ def compute_tree_layout(
24
24
  """Compute the layout for a tree.
25
25
 
26
26
  Parameters:
27
- layout: The name of the layout, e.g. "horizontal", "vertial", or "radial".
27
+ layout: The name of the layout, e.g. "horizontal", "vertical", or "radial".
28
28
  orientation: The orientation of the layout, e.g. "right", "left", "descending",
29
29
  "ascending", "clockwise", "anticlockwise".
30
30
 
@@ -38,6 +38,10 @@ def compute_tree_layout(
38
38
  kwargs["branch_length_fun"] = branch_length_fun
39
39
  kwargs["orientation"] = orientation
40
40
 
41
+ # Angular or not, the vertex layout is unchanged. Since we do not
42
+ # currently compute an edge layout here, we can ignore the option.
43
+ kwargs.pop("angular", None)
44
+
41
45
  if layout == "radial":
42
46
  layout_dict = _radial_tree_layout(**kwargs)
43
47
  elif layout == "horizontal":
iplotx/network.py CHANGED
@@ -1,4 +1,8 @@
1
- from typing import Optional, Sequence
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ Self,
5
+ )
2
6
  import numpy as np
3
7
  import pandas as pd
4
8
  import matplotlib as mpl
@@ -62,12 +66,6 @@ class NetworkArtist(mpl.artist.Artist):
62
66
 
63
67
  """
64
68
  self.network = network
65
- self._ipx_internal_data = ingest_network_data(
66
- network,
67
- layout,
68
- vertex_labels=vertex_labels,
69
- edge_labels=edge_labels,
70
- )
71
69
 
72
70
  super().__init__()
73
71
 
@@ -80,8 +78,65 @@ class NetworkArtist(mpl.artist.Artist):
80
78
  zorder = get_style(".network").get("zorder", 1)
81
79
  self.set_zorder(zorder)
82
80
 
83
- self._add_vertices()
84
- self._add_edges()
81
+ if network is not None:
82
+ self._ipx_internal_data = ingest_network_data(
83
+ network,
84
+ layout,
85
+ vertex_labels=vertex_labels,
86
+ edge_labels=edge_labels,
87
+ )
88
+
89
+ self._add_vertices()
90
+ self._add_edges()
91
+
92
+ @classmethod
93
+ def from_other(
94
+ cls: "NetworkArtist", # NOTE: This is fixed in Python 3.14
95
+ other: Self,
96
+ ) -> Self:
97
+ """Create a NetworkArtist as a copy of another one.
98
+
99
+ Parameters:
100
+ other: The other NetworkArtist.
101
+
102
+ Returns:
103
+ An instantiated NetworkArtist.
104
+ """
105
+ self = cls.from_edgecollection(other._edges)
106
+ self.network = other.network
107
+ self._ipx_internal_data = other._ipx_internal_data
108
+ return self
109
+
110
+ @classmethod
111
+ def from_edgecollection(
112
+ cls: "NetworkArtist", # NOTE: This is fixed in Python 3.14
113
+ edge_collection: EdgeCollection,
114
+ ) -> Self:
115
+ """Create a NetworkArtist from iplotx artists.
116
+
117
+ Parameters:
118
+ edge_collection: The edge collection to use to initialise the artist. Vertices will
119
+ be obtained automatically.
120
+
121
+ Returns:
122
+ The initialised NetworkArtist.
123
+ """
124
+ vertex_collection = edge_collection._vertex_collection
125
+ layout = vertex_collection._layout
126
+ transform = vertex_collection.get_transform()
127
+ offset_transform = edge_collection.get_transform()
128
+
129
+ # Follow the steps in the normal constructor
130
+ self = cls(
131
+ network=None,
132
+ layout=layout,
133
+ transform=transform,
134
+ offset_transform=offset_transform,
135
+ )
136
+ self._vertices = vertex_collection
137
+ self._edges = edge_collection
138
+
139
+ return self
85
140
 
86
141
  def get_children(self):
87
142
  return (self._vertices, self._edges)
@@ -189,6 +244,7 @@ class NetworkArtist(mpl.artist.Artist):
189
244
  cmap_fun = _build_cmap_fun(
190
245
  edge_style["color"],
191
246
  edge_style["cmap"],
247
+ edge_style.get("norm", None),
192
248
  )
193
249
  else:
194
250
  cmap_fun = None
@@ -231,7 +287,7 @@ class NetworkArtist(mpl.artist.Artist):
231
287
  edgepatches.append(patch)
232
288
  adjacent_vertex_ids.append((vid1, vid2))
233
289
 
234
- if "cmap" in edge_style:
290
+ if ("cmap" in edge_style) and ("norm" not in edge_style):
235
291
  vmin = np.min(colorarray)
236
292
  vmax = np.max(colorarray)
237
293
  norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
@@ -259,8 +315,6 @@ class NetworkArtist(mpl.artist.Artist):
259
315
  if not self.get_visible():
260
316
  return
261
317
 
262
- # FIXME: Callbacks on stale vertices/edges??
263
-
264
318
  # NOTE: looks like we have to manage the zorder ourselves
265
319
  # this is kind of funny actually
266
320
  children = list(self.get_children())
iplotx/plotting.py CHANGED
@@ -53,7 +53,7 @@ def network(
53
53
  used as a quick fix when some vertex shapes reach beyond the plot edge. This is
54
54
  a fraction of the data limits, so 0.1 means 10% of the data limits will be left
55
55
  as margin.
56
- **kwargs: Additional arguments are treated as an alternate way to specify style. If
56
+ kwargs: Additional arguments are treated as an alternate way to specify style. If
57
57
  both "style" and additional **kwargs are provided, they are both applied in that
58
58
  order (style, then **kwargs).
59
59
 
@@ -123,10 +123,10 @@ def network(
123
123
  def tree(
124
124
  tree: Optional[TreeType] = None,
125
125
  layout: str | LayoutType = "horizontal",
126
- orientation: Optional[str] = None,
127
126
  directed: bool | str = False,
128
- vertex_labels: Optional[list | dict | pd.Series] = None,
129
- leaf_labels: Optional[list | dict | pd.Series] = None,
127
+ vertex_labels: Optional[list | dict | pd.Series | bool] = None,
128
+ leaf_labels: Optional[list | dict | pd.Series | bool] = None,
129
+ show_support: bool = False,
130
130
  ax: Optional[mpl.axes.Axes] = None,
131
131
  style: str | dict | Sequence[str | dict] = "tree",
132
132
  title: Optional[str] = None,
@@ -139,11 +139,11 @@ def tree(
139
139
  Parameters:
140
140
  tree: The tree to plot. Can be a BioPython.Phylo.Tree object.
141
141
  layout: The layout to use for plotting.
142
- orientation: The orientation of the layout. Can be "right" or "left". Defaults to
143
- "right" for horizontal layout, "descending" or "ascending" for vertical layout,
144
- and "clockwise" or "anticlockwise" for radial layout.
145
142
  directed: If False, donot draw arrows. If True or "child", draw arrows from parent to child
146
143
  node. If "parent", draw arrows the other way around.
144
+ show_support: If True, show the support values for the nodes (assumed to be from 0 to 100,
145
+ rounded to nearest integer). If both this parameter and vertex_labels are set,
146
+ show_support takes precedence and hides the vertex labels.
147
147
 
148
148
  Returns:
149
149
  A TreeArtist object, set as a direct child of the matplotlib Axes.
@@ -157,12 +157,12 @@ def tree(
157
157
  artist = TreeArtist(
158
158
  tree=tree,
159
159
  layout=layout,
160
- orientation=orientation,
161
160
  directed=directed,
162
161
  transform=mpl.transforms.IdentityTransform(),
163
162
  offset_transform=ax.transData,
164
163
  vertex_labels=vertex_labels,
165
164
  leaf_labels=leaf_labels,
165
+ show_support=show_support,
166
166
  )
167
167
  ax.add_artist(artist)
168
168