text: from __future__ import absolute_import, print_function
  from builtins import object, str
  import copy, numpy, pandas, pyarrow as pa, sys, uuid
  
  from .pygraphistry import PyGraphistry
  from .pygraphistry import util
  from .pygraphistry import bolt_util
  from .nodexlistry import NodeXLGraphistry
  from .tigeristry import Tigeristry
  from .arrow_uploader import ArrowUploader
  
  maybe_cudf = None
  try:
      import cudf
      maybe_cudf = cudf
  except ImportError:
      1
  
  class Plotter(object):
      """Graph plotting class.
  
      Created using ``Graphistry.bind()``.
  
      Chained calls successively add data and visual encodings, and end with a plot call.
  
      To streamline reuse and replayable notebooks, Plotter manipulations are immutable. Each chained call returns a new instance that derives from the previous one. The old plotter or the new one can then be used to create different graphs.
  
      The class supports convenience methods for mixing calls across Pandas, NetworkX, and IGraph.
      """
  
  
      _defaultNodeId = '__nodeid__'
  
  
      def __init__(self):
          # Bindings
          self._edges = None
          self._nodes = None
          self._source = None
          self._destination = None
          self._node = None
          self._edge_title = None
          self._edge_label = None
          self._edge_color = None
          self._edge_source_color = None
          self._edge_destination_color = None
          self._edge_size = None
          self._edge_weight = None
          self._edge_icon = None
          self._edge_opacity = None
          self._point_title = None
          self._point_label = None
          self._point_color = None
          self._point_size = None
          self._point_weight = None
          self._point_icon = None
          self._point_opacity = None
          self._point_x = None
          self._point_y = None
          # Settings
          self._height = 500
          self._render = True
          self._url_params = {'info': 'true'}
          # Metadata
          self._name = None
          self._description = None
          # Integrations
          self._bolt_driver = None
          self._tigergraph = None
  
  
      def __repr__(self):
          bindings = ['edges', 'nodes', 'source', 'destination', 'node', 
                      'edge_label', 'edge_color', 'edge_size', 'edge_weight', 'edge_title', 'edge_icon', 'edge_opacity',
                      'edge_source_color', 'edge_destination_color',
                      'point_label', 'point_color', 'point_size', 'point_weight', 'point_title', 'point_icon', 'point_opacity',
                      'point_x', 'point_y']
          settings = ['height', 'url_params']
  
          rep = {'bindings': dict([(f, getattr(self, '_' + f)) for f in bindings]),
                 'settings': dict([(f, getattr(self, '_' + f)) for f in settings])}
          if util.in_ipython():
              from IPython.lib.pretty import pretty
              return pretty(rep)
          else:
              return str(rep)
  
  
      def bind(self, source=None, destination=None, node=None,
               edge_title=None, edge_label=None, edge_color=None, edge_weight=None, edge_size=None, edge_opacity=None, edge_icon=None,
               edge_source_color=None, edge_destination_color=None,
               point_title=None, point_label=None, point_color=None, point_weight=None, point_size=None, point_opacity=None, point_icon=None,
               point_x=None, point_y=None):
          """Relate data attributes to graph structure and visual representation.
  
          To facilitate reuse and replayable notebooks, the binding call is chainable. Invocation does not effect the old binding: it instead returns a new Plotter instance with the new bindings added to the existing ones. Both the old and new bindings can then be used for different graphs.
  
  
          :param source: Attribute containing an edge's source ID
          :type source: String.
  
          :param destination: Attribute containing an edge's destination ID
          :type destination: String.
  
          :param node: Attribute containing a node's ID
          :type node: String.
  
          :param edge_title: Attribute overriding edge's minimized label text. By default, the edge source and destination is used.
          :type edge_title: HtmlString.
  
          :param edge_label: Attribute overriding edge's expanded label text. By default, scrollable list of attribute/value mappings.
          :type edge_label: HtmlString.
  
          :param edge_color: Attribute overriding edge's color. rgba (int64) or int32 palette index, see palette definitions <https://graphistry.github.io/docs/legacy/api/0.9.2/api.html#extendedpalette>`_ for values. Based on Color Brewer.
          :type edge_color: int32 | int64.
  
          :param edge_source_color: Attribute overriding edge's source color if no edge_color, as an rgba int64 value.
          :type edge_source_color: int64.
  
          :param edge_destination_color: Attribute overriding edge's destination color if no edge_color, as an rgba int64 value.
          :type edge_destination_color: int64.
  
          :param edge_weight: Attribute overriding edge weight. Default is 1. Advanced layout controls will relayout edges based on this value.
          :type edge_weight: String.
  
          :param point_title: Attribute overriding node's minimized label text. By default, the node ID is used.
          :type point_title: HtmlString.
  
          :param point_label: Attribute overriding node's expanded label text. By default, scrollable list of attribute/value mappings.
          :type point_label: HtmlString.
  
          :param point_color: Attribute overriding node's color.rgba (int64) or int32 palette index, see palette definitions <https://graphistry.github.io/docs/legacy/api/0.9.2/api.html#extendedpalette>`_ for values. Based on Color Brewer.
          :type point_color: int32 | int64.
  
          :param point_size: Attribute overriding node's size. By default, uses the node degree. The visualization will normalize point sizes and adjust dynamically using semantic zoom.
          :type point_size: HtmlString.
  
          :param point_x: Attribute overriding node's initial x position. Combine with ".settings(url_params={'play': 0}))" to create a custom layout
          :type point_x: number.
  
          :param point_y: Attribute overriding node's initial y position. Combine with ".settings(url_params={'play': 0}))" to create a custom layout
          :type point_y: number.
  
          :returns: Plotter.
          :rtype: Plotter.
  
          **Example: Minimal**
              ::
  
                  import graphistry
                  g = graphistry.bind()
                  g = g.bind(source='src', destination='dst')
  
          **Example: Node colors**
              ::
  
                  import graphistry
                  g = graphistry.bind()
                  g = g.bind(source='src', destination='dst',
                             node='id', point_color='color')
  
          **Example: Chaining**
              ::
  
                  import graphistry
                  g = graphistry.bind(source='src', destination='dst', node='id')
  
                  g1 = g.bind(point_color='color1', point_size='size1')
  
                  g.bind(point_color='color1b')
  
                  g2a = g1.bind(point_color='color2a')
                  g2b = g1.bind(point_color='color2b', point_size='size2b')
  
                  g3a = g2a.bind(point_size='size3a')
                  g3b = g2b.bind(point_size='size3b')
  
          In the above **Chaining** example, all bindings use src/dst/id. Colors and sizes bind to:
              ::
  
                  g: default/default
                  g1: color1/size1
                  g2a: color2a/size1
                  g2b: color2b/size2b
                  g3a: color2a/size3a
                  g3b: color2b/size3b
  
  
          """
          res = copy.copy(self)
          res._source = source or self._source
          res._destination = destination or self._destination
          res._node = node or self._node
  
          res._edge_title = edge_title or self._edge_title
          res._edge_label = edge_label or self._edge_label
          res._edge_color = edge_color or self._edge_color
          res._edge_source_color = edge_source_color or self._edge_source_color
          res._edge_destination_color = edge_destination_color or self._edge_destination_color
          res._edge_size = edge_size or self._edge_size
          res._edge_weight = edge_weight or self._edge_weight
          res._edge_icon = edge_icon or self._edge_icon
          res._edge_opacity = edge_opacity or self._edge_opacity
  
          res._point_title = point_title or self._point_title
          res._point_label = point_label or self._point_label
          res._point_color = point_color or self._point_color
          res._point_size = point_size or self._point_size
          res._point_weight = point_weight or self._point_weight
          res._point_opacity = point_opacity or self._point_opacity
          res._point_icon = point_icon or self._point_icon
          res._point_x = point_x or self._point_x
          res._point_y = point_y or self._point_y
          
          return res
  
  
      def nodes(self, nodes):
          """Specify the set of nodes and associated data.
  
          Must include any nodes referenced in the edge list.
  
          :param nodes: Nodes and their attributes.
          :type point_size: Pandas dataframe
  
          :returns: Plotter.
          :rtype: Plotter.
  
          **Example**
              ::
  
                  import graphistry
  
                  es = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  g = graphistry
                      .bind(source='src', destination='dst')
                      .edges(es)
  
                  vs = pandas.DataFrame({'v': [0,1,2], 'lbl': ['a', 'b', 'c']})
                  g = g.bind(node='v').nodes(vs)
  
                  g.plot()
  
          """
  
  
          res = copy.copy(self)
          res._nodes = nodes
          return res
  
      def name(self, name):
          """Upload name
  
          :param name: Upload name
          :type name: str"""
  
          res = copy.copy(self)
          res._name = name
          return res
  
      def description(self, description):
          """Upload description
  
          :param description: Upload description
          :type description: str"""
  
          res = copy.copy(self)
          res._description = description
          return res
  
  
      def edges(self, edges):
          """Specify edge list data and associated edge attribute values.
  
          :param edges: Edges and their attributes.
          :type point_size: Pandas dataframe, NetworkX graph, or IGraph graph.
  
          :returns: Plotter.
          :rtype: Plotter.
  
          **Example**
              ::
  
                  import graphistry
                  df = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  graphistry
                      .bind(source='src', destination='dst')
                      .edges(df)
                      .plot()
  
          """
  
          res = copy.copy(self)
          res._edges = edges
          return res
  
  
      def graph(self, ig):
          """Specify the node and edge data.
  
          :param ig: Graph with node and edge attributes.
          :type ig: NetworkX graph or an IGraph graph.
  
          :returns: Plotter.
          :rtype: Plotter.
          """
  
          res = copy.copy(self)
          res._edges = ig
          res._nodes = None
          return res
  
  
      def settings(self, height=None, url_params={}, render=None):
          """Specify iframe height and add URL parameter dictionary.
  
          The library takes care of URI component encoding for the dictionary.
  
          :param height: Height in pixels.
          :type height: Integer.
  
          :param url_params: Dictionary of querystring parameters to append to the URL.
          :type url_params: Dictionary
  
          :param render: Whether to render the visualization using the native notebook environment (default True), or return the visualization URL
          :type render: Boolean
  
          """
  
          res = copy.copy(self)
          res._height = height or self._height
          res._url_params = dict(self._url_params, **url_params)
          res._render = self._render if render == None else render
          return res
  
  
      def plot(self, graph=None, nodes=None, name=None, description=None, render=None, skip_upload=False):
          """Upload data to the Graphistry server and show as an iframe of it.
  
          Uses the currently bound schema structure and visual encodings.
          Optional parameters override the current bindings.
  
          When used in a notebook environment, will also show an iframe of the visualization.
  
          :param graph: Edge table or graph.
          :type graph: Pandas dataframe, NetworkX graph, or IGraph graph.
  
          :param nodes: Nodes table.
          :type nodes: Pandas dataframe.
  
          :param name: Upload name.
          :type name: Optional str.
  
          :param description: Upload description.
          :type description: Optional str.
  
          :param render: Whether to render the visualization using the native notebook environment (default True), or return the visualization URL
          :type render: Boolean
  
          :param skip_upload: Return node/edge/bindings that would have been uploaded. By default, upload happens.
          :type skip_upload: Boolean. 
  
          **Example: Simple**
              ::
  
                  import graphistry
                  es = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  graphistry
                      .bind(source='src', destination='dst')
                      .edges(es)
                      .plot()
  
          **Example: Shorthand**
              ::
  
                  import graphistry
                  es = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  graphistry
                      .bind(source='src', destination='dst')
                      .plot(es)
  
          """
  
          if graph is None:
              if self._edges is None:
                  util.error('Graph/edges must be specified.')
              g = self._edges
          else:
              g = graph
          n = self._nodes if nodes is None else nodes
          name = name or self._name or ("Untitled " + util.random_string(10))
          description = description or self._description or ("")
  
          self._check_mandatory_bindings(not isinstance(n, type(None)))
  
          api_version = PyGraphistry.api_version()
          if api_version == 1:
              dataset = self._plot_dispatch(g, n, name, description, 'json')
              if skip_upload:
                  return dataset
              info = PyGraphistry._etl1(dataset)
          elif api_version == 2:
              dataset = self._plot_dispatch(g, n, name, description, 'vgraph')
              if skip_upload:
                  return dataset
              info = PyGraphistry._etl2(dataset)
          elif api_version == 3:
              PyGraphistry.refresh()
              dataset = self._plot_dispatch(g, n, name, description, 'arrow')
              if skip_upload:
                  return dataset
              #fresh
              dataset.token = PyGraphistry.api_token()
              dataset.post()
              info = {
                  'name': dataset.dataset_id,
                  'type': 'arrow',
                  'viztoken': str(uuid.uuid4())
              }
  
          viz_url = PyGraphistry._viz_url(info, self._url_params)
          cfg_client_protocol_hostname = PyGraphistry._config['client_protocol_hostname']
          full_url = ('%s:%s' % (PyGraphistry._config['protocol'], viz_url)) if cfg_client_protocol_hostname is None else viz_url
  
          if render == False or (render == None and not self._render):
              return full_url
          elif util.in_ipython():
              from IPython.core.display import HTML
              return HTML(util.make_iframe(full_url, self._height))
          else:
              import webbrowser
              webbrowser.open(full_url)
              return full_url
  
  
      def pandas2igraph(self, edges, directed=True):
          """Convert a pandas edge dataframe to an IGraph graph.
  
          Uses current bindings. Defaults to treating edges as directed.
  
          **Example**
              ::
  
                  import graphistry
                  g = graphistry.bind()
  
                  es = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  g = g.bind(source='src', destination='dst')
  
                  ig = g.pandas2igraph(es)
                  ig.vs['community'] = ig.community_infomap().membership
                  g.bind(point_color='community').plot(ig)
          """
  
  
          import igraph
          self._check_mandatory_bindings(False)
          self._check_bound_attribs(edges, ['source', 'destination'], 'Edge')
          
          self._node = self._node or Plotter._defaultNodeId
          eattribs = edges.columns.values.tolist()
          eattribs.remove(self._source)
          eattribs.remove(self._destination)
          cols = [self._source, self._destination] + eattribs
          etuples = [tuple(x) for x in edges[cols].values]
          return igraph.Graph.TupleList(etuples, directed=directed, edge_attrs=eattribs,
                                        vertex_name_attr=self._node)
  
  
      def igraph2pandas(self, ig):
          """Under current bindings, transform an IGraph into a pandas edges dataframe and a nodes dataframe.
  
          **Example**
              ::
  
                  import graphistry
                  g = graphistry.bind()
  
                  es = pandas.DataFrame({'src': [0,1,2], 'dst': [1,2,0]})
                  g = g.bind(source='src', destination='dst').edges(es)
  
                  ig = g.pandas2igraph(es)
                  ig.vs['community'] = ig.community_infomap().membership
  
                  (es2, vs2) = g.igraph2pandas(ig)
                  g.nodes(vs2).bind(point_color='community').plot()
          """
  
          def get_edgelist(ig):
              idmap = dict(enumerate(ig.vs[self._node]))
              for e in ig.es:
                  t = e.tuple
                  yield dict({self._source: idmap[t[0]], self._destination: idmap[t[1]]},
                              **e.attributes())
  
          self._check_mandatory_bindings(False)
          if self._node is None:
              ig.vs[Plotter._defaultNodeId] = [v.index for v in ig.vs]
              self._node = Plotter._defaultNodeId
          elif self._node not in ig.vs.attributes():
              util.error('Vertex attribute "%s" bound to "node" does not exist.' % self._node)
  
          edata = get_edgelist(ig)
          ndata = [v.attributes() for v in ig.vs]
          nodes = pandas.DataFrame(ndata, columns=ig.vs.attributes())
          cols = [self._source, self._destination] + ig.es.attributes()
          edges = pandas.DataFrame(edata, columns=cols)
          return (edges, nodes)
  
  
      def networkx_checkoverlap(self, g):
  
          import networkx as nx
          [x, y] = [int(x) for x in nx.__version__.split('.')]
  
          vattribs = None
          if x == 1:
              vattribs = g.nodes(data=True)[0][1] if g.number_of_nodes() > 0 else []
          else:
              vattribs = g.nodes(data=True) if g.number_of_nodes() > 0 else []
          if not (self._node is None) and self._node in vattribs:
              util.error('Vertex attribute "%s" already exists.' % self._node)
  
      def networkx2pandas(self, g):
  
          def get_nodelist(g):
              for n in g.nodes(data=True):
                  yield dict({self._node: n[0]}, **n[1])
          def get_edgelist(g):
              for e in g.edges(data=True):
                  yield dict({self._source: e[0], self._destination: e[1]}, **e[2])
  
          self._check_mandatory_bindings(False)
          self.networkx_checkoverlap(g)
          
          self._node = self._node or Plotter._defaultNodeId
          nodes = pandas.DataFrame(get_nodelist(g))
          edges = pandas.DataFrame(get_edgelist(g))
          return (edges, nodes)
  
  
      def _check_mandatory_bindings(self, node_required):
          if self._source is None or self._destination is None:
              util.error('Both "source" and "destination" must be bound before plotting.')
          if node_required and self._node is None:
              util.error('Node identifier must be bound when using node dataframe.')
  
  
      def _check_bound_attribs(self, df, attribs, typ):
          cols = df.columns.values.tolist()
          for a in attribs:
              b = getattr(self, '_' + a)
              if b not in cols:
                  util.error('%s attribute "%s" bound to "%s" does not exist.' % (typ, a, b))
  
  
      def _plot_dispatch(self, graph, nodes, name, description, mode='json'):
  
          if isinstance(graph, pandas.core.frame.DataFrame) \
              or isinstance(graph, pa.Table) \
              or ( not (maybe_cudf is None) and isinstance(graph, maybe_cudf.DataFrame) ):
              return self._make_dataset(graph, nodes, name, description, mode)
  
          try:
              import igraph
              if isinstance(graph, igraph.Graph):
                  (e, n) = self.igraph2pandas(graph)
                  return self._make_dataset(e, n, name, description, mode)
          except ImportError:
              pass
  
          try:
              import networkx
              if isinstance(graph, networkx.classes.graph.Graph) or \
                 isinstance(graph, networkx.classes.digraph.DiGraph) or \
                 isinstance(graph, networkx.classes.multigraph.MultiGraph) or \
                 isinstance(graph, networkx.classes.multidigraph.MultiDiGraph):
                  (e, n) = self.networkx2pandas(graph)
                  return self._make_dataset(e, n, name, description, mode)
          except ImportError:
              pass
  
          util.error('Expected Pandas/Arrow/cuDF dataframe(s) or Igraph/NetworkX graph.')
  
  
      # Sanitize node/edge dataframe by
      # - dropping indices
      # - dropping edges with NAs in source or destination
      # - dropping nodes with NAs in nodeid
      # - creating a default node table if none was provided.
      # - inferring numeric types of all columns containing numpy objects
      def _sanitize_dataset(self, edges, nodes, nodeid):
          self._check_bound_attribs(edges, ['source', 'destination'], 'Edge')
          elist = edges.reset_index(drop=True) \
                       .dropna(subset=[self._source, self._destination])
  
          obj_df = elist.select_dtypes(include=[numpy.object_])
          elist[obj_df.columns] = obj_df.apply(pandas.to_numeric, errors='ignore')
  
          if nodes is None:
              nodes = pandas.DataFrame()
              nodes[nodeid] = pandas.concat([edges[self._source], edges[self._destination]],
                                             ignore_index=True).drop_duplicates()
          else:
              self._check_bound_attribs(nodes, ['node'], 'Vertex')
  
          nlist = nodes.reset_index(drop=True) \
                       .dropna(subset=[nodeid]) \
                       .drop_duplicates(subset=[nodeid])
  
          obj_df = nlist.select_dtypes(include=[numpy.object_])
          nlist[obj_df.columns] = obj_df.apply(pandas.to_numeric, errors='ignore')
  
          return (elist, nlist)
  
  
      def _check_dataset_size(self, elist, nlist):
          edge_count = len(elist.index)
          node_count = len(nlist.index)
          graph_size = edge_count + node_count
          if edge_count > 8e6:
              util.error('Maximum number of edges (8M) exceeded: %d.' % edge_count)
          if node_count > 8e6:
              util.error('Maximum number of nodes (8M) exceeded: %d.' % node_count)
          if graph_size > 1e6:
              util.warn('Large graph: |nodes| + |edges| = %d. Layout/rendering might be slow.' % graph_size)
  
  
      # Bind attributes for ETL1 by creating a copy of the designated column renamed
      # with magic names understood by ETL1 (eg. pointColor, etc)
      def _bind_attributes_v1(self, edges, nodes):
          def bind(df, pbname, attrib, default=None):
              bound = getattr(self, attrib)
              if bound:
                  if bound in df.columns.tolist():
                      df[pbname] = df[bound]
                  else:
                      util.warn('Attribute "%s" bound to %s does not exist.' % (bound, attrib))
              elif default:
                  df[pbname] = df[default]
  
          nodeid = self._node or Plotter._defaultNodeId
          (elist, nlist) = self._sanitize_dataset(edges, nodes, nodeid)
          self._check_dataset_size(elist, nlist)
  
          bind(elist, 'edgeColor', '_edge_color')
          bind(elist, 'edgeSourceColor', '_edge_source_color')
          bind(elist, 'edgeDestinationColor', '_edge_destination_color')
          bind(elist, 'edgeLabel', '_edge_label')
          bind(elist, 'edgeTitle', '_edge_title')
          bind(elist, 'edgeSize', '_edge_size')
          bind(elist, 'edgeWeight', '_edge_weight')
          bind(elist, 'edgeOpacity', '_edge_opacity')
          bind(elist, 'edgeIcon', '_edge_icon')
          bind(nlist, 'pointColor', '_point_color')
          bind(nlist, 'pointLabel', '_point_label')
          bind(nlist, 'pointTitle', '_point_title', nodeid)
          bind(nlist, 'pointSize', '_point_size')
          bind(nlist, 'pointWeight', '_point_weight')
          bind(nlist, 'pointOpacity', '_point_opacity')
          bind(nlist, 'pointIcon', '_point_icon')
          bind(nlist, 'pointX', '_point_x')
          bind(nlist, 'pointY', '_point_y')
          return (elist, nlist)
  
      # Bind attributes for ETL2 by an encodings map storing the visual semantic of
      # each bound column.
      def _bind_attributes_v2(self, edges, nodes):
          def bind(enc, df, pbname, attrib, default=None):
              bound = getattr(self, attrib)
              if bound:
                  if bound in df.columns.tolist():
                      enc[pbname] = {'attributes' : [bound]}
                  else:
                      util.warn('Attribute "%s" bound to %s does not exist.' % (bound, attrib))
              elif default:
                  enc[pbname] = {'attributes': [default]}
  
          nodeid = self._node or Plotter._defaultNodeId
          (elist, nlist) = self._sanitize_dataset(edges, nodes, nodeid)
          self._check_dataset_size(elist, nlist)
  
          edge_encodings = {
              'source': {'attributes' : [self._source]},
              'destination': {'attributes': [self._destination]},
          }
          node_encodings = {
              'nodeId': {'attributes': [nodeid]}
          }
          bind(edge_encodings, elist, 'edgeColor', '_edge_color')
          bind(edge_encodings, elist, 'edgeSourceColor', '_edge_source_color')
          bind(edge_encodings, elist, 'edgeDestinationColor', '_edge_destination_color')
          bind(edge_encodings, elist, 'edgeLabel', '_edge_label')
          bind(edge_encodings, elist, 'edgeTitle', '_edge_title')
          bind(edge_encodings, elist, 'edgeSize', '_edge_size')
          bind(edge_encodings, elist, 'edgeWeight', '_edge_weight')
          bind(edge_encodings, elist, 'edgeOpacity', '_edge_opacity')
          bind(edge_encodings, elist, 'edgeIcon', '_edge_icon')
          bind(node_encodings, nlist, 'pointColor', '_point_color')
          bind(node_encodings, nlist, 'pointLabel', '_point_label')
          bind(node_encodings, nlist, 'pointTitle', '_point_title', nodeid)
          bind(node_encodings, nlist, 'pointSize', '_point_size')
          bind(node_encodings, nlist, 'pointWeight', '_point_weight')
          bind(node_encodings, nlist, 'pointOpacity', '_point_opacity')
          bind(node_encodings, nlist, 'pointIcon', '_point_icon')
          bind(node_encodings, nlist, 'pointX', '_point_x')
          bind(node_encodings, nlist, 'pointY', '_point_y')
  
          encodings = {
              'nodes': node_encodings,
              'edges': edge_encodings
          }
          return (elist, nlist, encodings)
  
      def _table_to_pandas(self, table) -> pandas.DataFrame:
  
          if table is None:
              return table
  
          if isinstance(table, pandas.DataFrame):
              return table
  
          if isinstance(table, pa.Table):
              return table.to_pandas()
          
          if not (maybe_cudf is None) and isinstance(table, maybe_cudf.DataFrame):
              return table.to_pandas()
          
          raise Exception('Unknown type %s: Could not convert data to Pandas dataframe' % str(type(table)))
  
      def _table_to_arrow(self, table) -> pa.Table:
  
          if table is None:
              return table
  
          if isinstance(table, pa.Table):
              return table
          
          if isinstance(table, pandas.DataFrame):
              return pa.Table.from_pandas(table, preserve_index=False).replace_schema_metadata({})
  
          if not (maybe_cudf is None) and isinstance(table, maybe_cudf.DataFrame):
              return table.to_arrow()
          
          raise Exception('Unknown type %s: Could not convert data to Arrow' % str(type(table)))
  
  
      def _make_dataset(self, edges, nodes, name, description, mode):
          try:
              if len(edges) == 0:
                  util.warn('Graph has no edges, may have rendering issues')
          except:
              1
  
          if mode == 'json':
              edges_df = self._table_to_pandas(edges)
              nodes_df = self._table_to_pandas(nodes)
              return self._make_json_dataset(edges_df, nodes_df, name)
          elif mode == 'vgraph':
              edges_df = self._table_to_pandas(edges)
              nodes_df = self._table_to_pandas(nodes)
              return self._make_vgraph_dataset(edges_df, nodes_df, name)
          elif mode == 'arrow':
              edges_arr = self._table_to_arrow(edges)
              nodes_arr = self._table_to_arrow(nodes)
              return self._make_arrow_dataset(edges_arr, nodes_arr, name, description)
              #token=None, dataset_id=None, url_params = None)
          else:
              raise ValueError('Unknown mode: ' + mode)
  
  
      # Main helper for creating ETL1 payload
      def _make_json_dataset(self, edges, nodes, name):
          (elist, nlist) = self._bind_attributes_v1(edges, nodes)
          edict = elist.where((pandas.notnull(elist)), None).to_dict(orient='records')
  
          bindings = {'idField': self._node or Plotter._defaultNodeId,
                      'destinationField': self._destination, 'sourceField': self._source}
          dataset = {'name': PyGraphistry._config['dataset_prefix'] + name,
                     'bindings': bindings, 'type': 'edgelist', 'graph': edict}
  
          if nlist is not None:
              ndict = nlist.where((pandas.notnull(nlist)), None).to_dict(orient='records')
              dataset['labels'] = ndict
          return dataset
  
  
      # Main helper for creating ETL2 payload
      def _make_vgraph_dataset(self, edges, nodes, name):
          from . import vgraph
  
          (elist, nlist, encodings) = self._bind_attributes_v2(edges, nodes)
          nodeid = self._node or Plotter._defaultNodeId
  
          sources = elist[self._source]
          dests = elist[self._destination]
          elist.drop([self._source, self._destination], axis=1, inplace=True)
  
          # Filter out nodes which have no edges
          lnodes = pandas.concat([sources, dests], ignore_index=True).unique()
          lnodes_df = pandas.DataFrame(lnodes, columns=[nodeid])
          filtered_nlist = pandas.merge(lnodes_df, nlist, on=nodeid, how='left')
  
          # Create a map from nodeId to a continuous range of integer [0, #nodes-1].
          # The vgraph protobuf format uses the continous integer ranger as internal nodeIds.
          node_map = dict([(v, i) for i, v in enumerate(lnodes.tolist())])
  
          dataset = vgraph.create(elist, filtered_nlist, sources, dests, nodeid, node_map, name)
          dataset['encodings'] = encodings
          return dataset
  
      def _make_arrow_dataset(self, edges: pa.Table, nodes: pa.Table, name: str, description: str) -> ArrowUploader:
          au = ArrowUploader(
              server_base_path=PyGraphistry.protocol() + '://' + PyGraphistry.server(),
              edges=edges, nodes=nodes,
              name=name, description=description,
              metadata={
                  'usertag': PyGraphistry._tag,
                  'key': PyGraphistry.api_key(),
                  'agent': 'pygraphistry',
                  'apiversion' : '3',
                  'agentversion': sys.modules['graphistry'].__version__,
              },
              certificate_validation=PyGraphistry.certificate_validation())
          au.edge_encodings = au.g_to_edge_encodings(self)
          au.node_encodings = au.g_to_node_encodings(self)
          return au
  
      def bolt(self, driver):
          res = copy.copy(self)
          res._bolt_driver = bolt_util.to_bolt_driver(driver)
          return res
  
  
      def cypher(self, query, params={}):
          res = copy.copy(self)
          driver = self._bolt_driver or PyGraphistry._config['bolt_driver']
          with driver.session() as session:
              bolt_statement = session.run(query, **params)
              graph = bolt_statement.graph()
              edges = bolt_util.bolt_graph_to_edges_dataframe(graph)
              nodes = bolt_util.bolt_graph_to_nodes_dataframe(graph)
          return res\
              .bind(\
                  node=bolt_util.node_id_key,\
                  source=bolt_util.start_node_id_key,\
                  destination=bolt_util.end_node_id_key
              )\
              .nodes(nodes)\
              .edges(edges)
  
      def nodexl(self, xls_or_url, source='default', engine=None, verbose=False):
          
          if not (engine is None):
              print('WARNING: Engine currently ignored, please contact if critical')
          
          return NodeXLGraphistry(self, engine).xls(xls_or_url, source, verbose)
  
  
      def tigergraph(self,
          protocol = 'http',
          server = 'localhost',
          web_port = 14240,
          api_port = 9000,
          db = None,
          user = 'tigergraph',
          pwd = 'tigergraph',
          verbose = False
      ):
          """Register Tigergraph connection setting defaults
      
          :param protocol: Protocol used to contact the database.
          :type protocol: Optional string.
          :param server: Domain of the database
          :type server: Optional string.
          :param web_port: 
          :type web_port: Optional integer.
          :param api_port: 
          :type api_port: Optional integer.
          :param db: Name of the database
          :type db: Optional string.    
          :param user:
          :type user: Optional string.    
          :param pwd: 
          :type pwd: Optional string.
          :param verbose: Whether to print operations
          :type verbose: Optional bool.         
          :returns: Plotter.
          :rtype: Plotter.
  
  
          **Example: Standard**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph(protocol='https', server='acme.com', db='my_db', user='alice', pwd='tigergraph2')                    
  
          """
          res = copy.copy(self)
          res._tigergraph = Tigeristry(self, protocol, server, web_port, api_port, db, user, pwd, verbose)
          return res
  
  
      def gsql_endpoint(self, method_name, args = {}, bindings = {}, db = None, dry_run = False):
          """Invoke Tigergraph stored procedure at a user-definend endpoint and return transformed Plottable
      
          :param method_name: Stored procedure name
          :type method_name: String.
          :param args: Named endpoint arguments
          :type args: Optional dictionary.
          :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList
          :type bindings: Optional dictionary.
          :param db: Name of the database, defaults to value set in .tigergraph(...)
          :type db: Optional string.
          :param dry_run: Return target URL without running
          :type dry_run: Bool, defaults to False
          :returns: Plotter.
          :rtype: Plotter.
  
          **Example: Minimal**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph(db='my_db')
                      tg.gsql_endpoint('neighbors').plot()
  
          **Example: Full**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph()
                      tg.gsql_endpoint('neighbors', {'k': 2}, {'edges': 'my_edge_list'}, 'my_db').plot()
  
          **Example: Read data**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph()
                      out = tg.gsql_endpoint('neighbors')
                      (nodes_df, edges_df) = (out._nodes, out._edges)
  
          """
          return self._tigergraph.gsql_endpoint(self, method_name, args, bindings, db, dry_run)
  
      def gsql(self, query, bindings = {}, dry_run = False):
          """Run Tigergraph query in interpreted mode and return transformed Plottable
      
          :param query: Code to run
          :type query: String.
          :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList
          :type bindings: Optional dictionary.
          :param dry_run: Return target URL without running
          :type dry_run: Bool, defaults to False        
          :returns: Plotter.
          :rtype: Plotter.
  
          **Example: Minimal**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph()
                      tg.gsql(\"\"\"
  INTERPRET QUERY () FOR GRAPH Storage { 
      
      OrAccum<BOOL> @@stop;
      ListAccum<EDGE> @@edgeList;
      SetAccum<vertex> @@set;
      
      @@set += to_vertex("61921", "Pool");
  
      Start = @@set;
  
      while Start.size() > 0 and @@stop == false do
  
        Start = select t from Start:s-(:e)-:t
        where e.goUpper == TRUE
        accum @@edgeList += e
        having t.type != "Service";
      end;
  
      print @@edgeList;
    }
                      \"\"\").plot()
  
         **Example: Full**
                  ::
  
                      import graphistry
                      tg = graphistry.tigergraph()
                      tg.gsql(\"\"\"
  INTERPRET QUERY () FOR GRAPH Storage { 
      
      OrAccum<BOOL> @@stop;
      ListAccum<EDGE> @@edgeList;
      SetAccum<vertex> @@set;
      
      @@set += to_vertex("61921", "Pool");
  
      Start = @@set;
  
      while Start.size() > 0 and @@stop == false do
  
        Start = select t from Start:s-(:e)-:t
        where e.goUpper == TRUE
        accum @@edgeList += e
        having t.type != "Service";
      end;
  
      print @@my_edge_list;
    }
                      \"\"\", {'edges': 'my_edge_list'}).plot()
          """        
          return self._tigergraph.gsql(self, query, bindings, dry_run)
  
  
  
  
  
  
  
  