# Buggy code
def __init__(self, data, kdims=None, vdims=None, **params):
    if isinstance(data, tuple):
        data = data + (None,) * (3 - len(data))
        edges, nodes, edgepaths = data
    else:
        edges, nodes, edgepaths = data, None, None
    if nodes is None:
        if isinstance(edges, list) and len(edges) == 0:
            nodes = []
        else:
            raise ValueError("TriMesh expects both simplices and nodes to be supplied.")

    if isinstance(nodes, self._node_type):
        pass
    elif isinstance(nodes, Points):
        # Add index to make it a valid Nodes object
        nodes = self._node_type(
            Dataset(nodes).add_dimension("index", 2, np.arange(len(nodes)))
        )
    elif not isinstance(nodes, Dataset) or nodes.ndims in [2, 3]:
        try:
            # Try assuming data contains indices (3 columns)
            nodes = self._node_type(nodes)
        except:
            # Try assuming data contains just coordinates (2 columns)
            try:
                points = Points(nodes)
                ds = Dataset(points).add_dimension("index", 2, np.arange(len(points)))
                nodes = self._node_type(ds)
            except:
                raise ValueError(
                    "Nodes argument could not be interpreted, expected "
                    "data with two or three columns representing the "
                    "x/y positions and optionally the node indices."
                )
    if edgepaths is not None and not isinstance(edgepaths, self._edge_type):
        edgepaths = self._edge_type(edgepaths)
    super(TriMesh, self).__init__(edges, kdims=kdims, vdims=vdims, **params)
    self._nodes = nodes
    self._edgepaths = edgepaths


# Traceback (ValueError)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-1e2470d0220f> in <module>()
----> 1 learner.plot().redim(x='a', y='b')

/opt/conda/lib/python3.6/site-packages/holoviews/core/dimension.py in __call__(self, specs, **dimensions)
131         if parent._deep_indexable and self.mode != 'dataset':
132             deep_mapped = [(k, v.redim(specs, **dimensions))
--> 133                            for k, v in parent.items()]
134             redimmed = parent.clone(deep_mapped)
135

/opt/conda/lib/python3.6/site-packages/holoviews/core/dimension.py in <listcomp>(.0)
131         if parent._deep_indexable and self.mode != 'dataset':
132             deep_mapped = [(k, v.redim(specs, **dimensions))
--> 133                            for k, v in parent.items()]
134             redimmed = parent.clone(deep_mapped)
135

/opt/conda/lib/python3.6/site-packages/holoviews/element/graphs.py in __call__(self, specs, **dimensions)
31         if self.parent._edgepaths:
32             new_data = new_data + (self.parent.edgepaths.redim(specs, **dimensions),)
---> 33         return redimmed.clone(new_data)
34
35

/opt/conda/lib/python3.6/site-packages/holoviews/element/graphs.py in clone(self, data, shared_data, new_type, *args, **overrides)
215             if self._edgepaths:
216                 data = data + (self.edgepaths,)
--> 217         return super(Graph, self).clone(data, shared_data, new_type, *args, **overrides)
218
219

/opt/conda/lib/python3.6/site-packages/holoviews/core/dimension.py in clone(self, data, shared_data, new_type, *args, **overrides)
570         # Apply name mangling for __ attribute
571         pos_args = getattr(self, '_' + type(self).__name__ + '__pos_params', [])
--> 572         return clone_type(data, *args, **{k:v for k,v in settings.items()
573                                           if k not in pos_args})
574

/opt/conda/lib/python3.6/site-packages/holoviews/element/graphs.py in __init__(self, data, kdims, vdims, **params)
429                 nodes = []
430             else:
--> 431                 raise ValueError("TriMesh expects both simplices and nodes "
432                                  "to be supplied.")
433

ValueError: TriMesh expects both simplices and nodes to be supplied.

# Fixed code
def __init__(self, data, kdims=None, vdims=None, **params):
    if isinstance(data, tuple):
        data = data + (None,) * (3 - len(data))
        edges, nodes, edgepaths = data
    else:
        edges, nodes, edgepaths = data, None, None

    super(TriMesh, self).__init__(edges, kdims=kdims, vdims=vdims, **params)
    if nodes is None:
        if len(self) == 0:
            nodes = []
        else:
            raise ValueError("TriMesh expects both simplices and nodes to be supplied.")

    if isinstance(nodes, self._node_type):
        pass
    elif isinstance(nodes, Points):
        # Add index to make it a valid Nodes object
        nodes = self._node_type(
            Dataset(nodes).add_dimension("index", 2, np.arange(len(nodes)))
        )
    elif not isinstance(nodes, Dataset) or nodes.ndims in [2, 3]:
        try:
            # Try assuming data contains indices (3 columns)
            nodes = self._node_type(nodes)
        except:
            # Try assuming data contains just coordinates (2 columns)
            try:
                points = Points(nodes)
                ds = Dataset(points).add_dimension("index", 2, np.arange(len(points)))
                nodes = self._node_type(ds)
            except:
                raise ValueError(
                    "Nodes argument could not be interpreted, expected "
                    "data with two or three columns representing the "
                    "x/y positions and optionally the node indices."
                )
    if edgepaths is not None and not isinstance(edgepaths, self._edge_type):
        edgepaths = self._edge_type(edgepaths)

    self._nodes = nodes
    self._edgepaths = edgepaths