Skip to content
13 changes: 11 additions & 2 deletions hatchet/external/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@
import numpy as np
import warnings
from ..util.colormaps import ColorMaps
from ..util.perf_measure import annotate


_console_class_annotate = annotate(fmt="HatchetConsoleRenderer.{}")


class ConsoleRenderer:
@_console_class_annotate
def __init__(self, unicode=False, color=False):
self.unicode = unicode
self.color = color
self.visited = []

@_console_class_annotate
def render(self, roots, dataframe, **kwargs):
self.render_header = kwargs["render_header"]

Expand Down Expand Up @@ -161,6 +167,7 @@ def render(self, roots, dataframe, **kwargs):
return result.encode("utf-8")

# pylint: disable=W1401
@_console_class_annotate
def render_preamble(self):
lines = [
r" __ __ __ __ ",
Expand All @@ -174,6 +181,7 @@ def render_preamble(self):

return "\n".join(lines)

@_console_class_annotate
def render_legend(self):
def render_label(index, low, high):
metric_range = self.max_metric - self.min_metric
Expand Down Expand Up @@ -247,6 +255,7 @@ def render_label(index, low, high):

return legend

@_console_class_annotate
def render_frame(self, node, dataframe, indent="", child_indent=""):
node_depth = node._depth
if node_depth < self.depth:
Expand Down Expand Up @@ -288,8 +297,8 @@ def render_frame(self, node, dataframe, indent="", child_indent=""):
"none": "",
"constant": "\U00002192",
"phased": "\U00002933",
"dynamic": "\U000021DD",
"sporadic": "\U0000219D",
"dynamic": "\U000021dd",
"sporadic": "\U0000219d",
}
pattern_metric = dataframe.loc[df_index, self.annotation_column]
annotation_content = self.temporal_symbols[pattern_metric]
Expand Down
17 changes: 17 additions & 0 deletions hatchet/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

from functools import total_ordering

from .util.perf_measure import annotate


_frame_annotate = annotate(fmt="Frame.{}")


@total_ordering
class Frame:
Expand All @@ -14,6 +19,7 @@ class Frame:
attrs (dict): dictionary of attributes and values
"""

@_frame_annotate
def __init__(self, attrs=None, **kwargs):
"""Construct a frame from a dictionary, or from immediate kwargs.

Expand Down Expand Up @@ -48,41 +54,52 @@ def __init__(self, attrs=None, **kwargs):

self._tuple_repr = None

@_frame_annotate
def __eq__(self, other):
return self.tuple_repr == other.tuple_repr

@_frame_annotate
def __lt__(self, other):
return self.tuple_repr < other.tuple_repr

@_frame_annotate
def __gt__(self, other):
return self.tuple_repr > other.tuple_repr

@_frame_annotate
def __hash__(self):
return hash(self.tuple_repr)

@_frame_annotate
def __str__(self):
"""str() with sorted attributes, so output is deterministic."""
return "{%s}" % ", ".join("'%s': '%s'" % (k, v) for k, v in self.tuple_repr)

@_frame_annotate
def __repr__(self):
return "Frame(%s)" % self

@property
@_frame_annotate
def tuple_repr(self):
"""Make a tuple of attributes and values based on reader."""
if not self._tuple_repr:
self._tuple_repr = tuple(sorted((k, v) for k, v in self.attrs.items()))
return self._tuple_repr

@_frame_annotate
def copy(self):
return Frame(self.attrs.copy())

@_frame_annotate
def __getitem__(self, name):
return self.attrs[name]

@_frame_annotate
def get(self, name, default=None):
return self.attrs.get(name, default)

@_frame_annotate
def values(self, names):
"""Return a tuple of attribute values from this Frame."""
if isinstance(names, (list, tuple)):
Expand Down
20 changes: 20 additions & 0 deletions hatchet/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
from collections import defaultdict

from .node import Node, traversal_order, node_traversal_order
from .util.perf_measure import annotate


_graph_annotate = annotate(fmt="Graph.{}")


@annotate()
def index_by(attr, objects):
"""Put objects into lists based on the value of an attribute.

Expand All @@ -23,11 +28,13 @@ def index_by(attr, objects):
class Graph:
"""A possibly multi-rooted tree or graph from one input dataset."""

@_graph_annotate
def __init__(self, roots):
assert roots is not None
self.roots = roots
self.node_ordering = False

@_graph_annotate
def traverse(self, order="pre", attrs=None, visited=None):
"""Preorder traversal of all roots of this Graph.

Expand All @@ -47,6 +54,7 @@ def traverse(self, order="pre", attrs=None, visited=None):
for value in root.traverse(order=order, attrs=attrs, visited=visited):
yield value

@_graph_annotate
def node_order_traverse(self, order="pre", attrs=None, visited=None):
"""Preorder traversal of all roots of this Graph, sorting by "node order" column.

Expand All @@ -69,6 +77,7 @@ def node_order_traverse(self, order="pre", attrs=None, visited=None):
):
yield value

@_graph_annotate
def is_tree(self):
"""True if this graph is a tree, false otherwise."""
if len(self.roots) > 1:
Expand All @@ -78,6 +87,7 @@ def is_tree(self):
list(self.traverse(visited=visited))
return all(v == 1 for v in visited.values())

@_graph_annotate
def find_merges(self):
"""Find nodes that have the same parent and frame.

Expand Down Expand Up @@ -135,6 +145,7 @@ def _find_child_merges(node_list):

return merges

@_graph_annotate
def merge_nodes(self, merges):
"""Merge some nodes in a graph into others.

Expand All @@ -159,11 +170,13 @@ def transform(node_list):
child.parents = transform(child.parents)
self.roots = transform(self.roots)

@_graph_annotate
def normalize(self):
merges = self.find_merges()
self.merge_nodes(merges)
return merges

@_graph_annotate
def copy(self, old_to_new=None):
"""Create and return a copy of this graph.

Expand Down Expand Up @@ -192,6 +205,7 @@ def copy(self, old_to_new=None):

return graph

@_graph_annotate
def union(self, other, old_to_new=None):
"""Create the union of self and other and return it as a new Graph.

Expand Down Expand Up @@ -342,6 +356,7 @@ def connect(parent, new_node):

return graph

@_graph_annotate
def enumerate_depth(self):
def _iter_depth(node, visited):
for child in node.children:
Expand All @@ -356,6 +371,7 @@ def _iter_depth(node, visited):
root._depth = 0 # depth of root node is 0
_iter_depth(root, visited)

@_graph_annotate
def enumerate_traverse(self):
if not self._check_enumerate_traverse():
# if "node order" column exists, we traverse sorting by _hatchet_nid
Expand All @@ -379,10 +395,12 @@ def _check_enumerate_traverse(self):
if i != node._hatchet_nid:
return False

@_graph_annotate
def __len__(self):
"""Size of the graph in terms of number of nodes."""
return sum(1 for _ in self.traverse())

@_graph_annotate
def __eq__(self, other):
"""Check if two graphs have the same structure by comparing frame at each
node.
Expand Down Expand Up @@ -415,10 +433,12 @@ def __eq__(self, other):

return True

@_graph_annotate
def __ne__(self, other):
return not (self == other)

@staticmethod
@_graph_annotate
def from_lists(*roots):
"""Convenience method to invoke Node.from_lists() on each root value."""
if not all(isinstance(r, (list, tuple)) for r in roots):
Expand Down
Loading