content: from __future__ import absolute_import
  
  from collections import Iterable, OrderedDict, Sequence
  import difflib
  import itertools
  import re
  import textwrap
  import warnings
  
  import numpy as np
  import sys
  from six import string_types, reraise
  
  from ..models import (
      BoxSelectTool, BoxZoomTool, CategoricalAxis,
      TapTool, CrosshairTool, DataRange1d, DatetimeAxis,
      FactorRange, Grid, HelpTool, HoverTool, LassoSelectTool, Legend, LegendItem, LinearAxis,
      LogAxis, PanTool, ZoomInTool, ZoomOutTool, PolySelectTool, ContinuousTicker,
      SaveTool, Range, Range1d, UndoTool, RedoTool, ResetTool, ResizeTool, Tool,
      WheelPanTool, WheelZoomTool, ColumnarDataSource, ColumnDataSource, GlyphRenderer,
      LogScale, LinearScale, CategoricalScale)
  
  from ..core.properties import ColorSpec, Datetime, value, field
  from ..transform import stack
  from ..util.dependencies import import_optional
  from ..util.deprecation import deprecated
  from ..util.string import nice_join
  
  pd = import_optional('pandas')
  
  DEFAULT_PALETTE = ["#f22c40", "#5ab738", "#407ee7", "#df5320", "#00ad9c", "#c33ff3"]
  
  
  def _stack(stackers, spec0, spec1, **kw):
      for name in (spec0, spec1):
          if name in kw:
              raise ValueError("Stack property '%s' cannot appear in keyword args" % name)
  
      lengths = { len(x) for x in kw.values() if isinstance(x, (list, tuple)) }
  
      # lengths will be empty if there are no kwargs supplied at all
      if len(lengths) > 0:
          if len(lengths) != 1:
              raise ValueError("Keyword argument sequences for broadcasting must all be the same lengths. Got lengths: %r" % sorted(list(lengths)))
          if lengths.pop() != len(stackers):
              raise ValueError("Keyword argument sequences for broadcasting must be the same length as stackers")
  
      s0 = []
      s1 = []
  
      _kw = []
  
      for i, val in enumerate(stackers):
          d  = {}
          s0 = list(s1)
          s1.append(val)
  
          d[spec0] = stack(*s0)
          d[spec1] = stack(*s1)
  
          for k, v in kw.items():
              if isinstance(v, (list, tuple)):
                  d[k] = v[i]
              else:
                  d[k] = v
  
          _kw.append(d)
  
      return _kw
  
  def get_default_color(plot=None):
      colors = [
          "#1f77b4",
          "#ff7f0e", "#ffbb78",
          "#2ca02c", "#98df8a",
          "#d62728", "#ff9896",
          "#9467bd", "#c5b0d5",
          "#8c564b", "#c49c94",
          "#e377c2", "#f7b6d2",
          "#7f7f7f",
          "#bcbd22", "#dbdb8d",
          "#17becf", "#9edae5"
      ]
      if plot:
          renderers = plot.renderers
          renderers = [x for x in renderers if x.__view_model__ == "GlyphRenderer"]
          num_renderers = len(renderers)
          return colors[num_renderers]
      else:
          return colors[0]
  
  
  def get_default_alpha(plot=None):
      return 1.0
  
  
  def _pop_renderer_args(kwargs):
      result = dict(data_source=kwargs.pop('source', ColumnDataSource()))
      for attr in ['name', 'x_range_name', 'y_range_name', 'level', 'view', 'visible', 'muted']:
          val = kwargs.pop(attr, None)
          if val:
              result[attr] = val
      return result
  
  
  def _pop_colors_and_alpha(glyphclass, kwargs, prefix="", default_alpha=1.0):
      """
      Given a kwargs dict, a prefix, and a default value, looks for different
      color and alpha fields of the given prefix, and fills in the default value
      if it doesn't exist.
      """
      result = dict()
  
      # TODO: The need to do this and the complexity of managing this kind of
      # thing throughout the codebase really suggests that we need to have
      # a real stylesheet class, where defaults and Types can declaratively
      # substitute for this kind of imperative logic.
      color = kwargs.pop(prefix + "color", get_default_color())
      for argname in ("fill_color", "line_color"):
          if argname not in glyphclass.properties():
              continue
          result[argname] = kwargs.pop(prefix + argname, color)
  
      # NOTE: text fill color should really always default to black, hard coding
      # this here now until the stylesheet solution exists
      if "text_color" in glyphclass.properties():
          result["text_color"] = kwargs.pop(prefix + "text_color", "black")
  
      alpha = kwargs.pop(prefix + "alpha", default_alpha)
      for argname in ("fill_alpha", "line_alpha", "text_alpha"):
          if argname not in glyphclass.properties():
              continue
          result[argname] = kwargs.pop(prefix + argname, alpha)
  
      return result
  
  
  def _get_legend_item_label(kwargs):
      legend = kwargs.pop('legend', None)
      source = kwargs.get('source')
      legend_item_label = None
      if legend:
          if isinstance(legend, string_types):
              # Do the simple thing first
              legend_item_label = value(legend)
              # But if there's a source - try and do something smart
              if source and hasattr(source, 'column_names'):
                  if legend in source.column_names:
                      legend_item_label = field(legend)
          else:
              legend_item_label = legend
      return legend_item_label
  
  
  _GLYPH_SOURCE_MSG = """
  Supplying a user-defined data source AND iterable values to glyph methods is deprecated.
  
  See https://github.com/bokeh/bokeh/issues/2056 for more information.
  """
  
  
  def _process_sequence_literals(glyphclass, kwargs, source, is_user_source):
      dataspecs = glyphclass.dataspecs_with_props()
      for var, val in kwargs.items():
  
          # ignore things that are not iterable
          if not isinstance(val, Iterable):
              continue
          # pass dicts (i.e., values or fields) on as-is
          if isinstance(val, dict):
              continue
          # let any non-dataspecs do their own validation (e.g., line_dash properties)
          if var not in dataspecs:
              continue
          # strings sequences are handled by the dataspec as-is
          if isinstance(val, string_types):
              continue
          # similarly colorspecs handle color tuple sequences as-is
          if (isinstance(dataspecs[var].property, ColorSpec) and isinstance(val, tuple)):
              continue
  
          if isinstance(val, np.ndarray) and val.ndim != 1:
              raise RuntimeError("Columns need to be 1D (%s is not)" % var)
  
          if is_user_source:
              deprecated(_GLYPH_SOURCE_MSG)
  
          source.add(val, name=var)
          kwargs[var] = var
  
  
  def _make_glyph(glyphclass, kws, extra):
      if extra is None:
          return None
      kws = kws.copy()
      kws.update(extra)
      return glyphclass(**kws)
  
  
  def _update_legend(plot, legend_item_label, glyph_renderer):
      # Get the plot's legend
      legends = plot.select(type=Legend)
      if not legends:
          legend = Legend()
          plot.add_layout(legend)
      elif len(legends) == 1:
          legend = legends[0]
      else:
          raise RuntimeError("Plot %s configured with more than one legend renderer" % plot)
  
      # If there is an existing legend with a matching label, then put the
      # renderer on that (if the source matches). Otherwise add a new one.
      added = False
      for item in legend.items:
          if item.label == legend_item_label:
              if item.label.get('value'):
                  item.renderers.append(glyph_renderer)
                  added = True
                  break
              if item.label.get('field') and \
                      glyph_renderer.data_source is item.renderers[0].data_source:
                  item.renderers.append(glyph_renderer)
                  added = True
                  break
      if not added:
          new_item = LegendItem(label=legend_item_label, renderers=[glyph_renderer])
          legend.items.append(new_item)
  
  
  def _get_range(range_input):
      if range_input is None:
          return DataRange1d()
      if pd and isinstance(range_input, pd.core.groupby.GroupBy):
          return FactorRange(factors=sorted(list(range_input.groups.keys())))
      if isinstance(range_input, Range):
          return range_input
      if isinstance(range_input, Sequence):
          if all(isinstance(x, string_types) for x in range_input):
              return FactorRange(factors=list(range_input))
          if len(range_input) == 2:
              try:
                  return Range1d(start=range_input[0], end=range_input[1])
              except ValueError:  # @mattpap suggests ValidationError instead
                  pass
      raise ValueError("Unrecognized range input: '%s'" % str(range_input))
  
  
  def _get_scale(range_input, axis_type):
      if isinstance(range_input, (DataRange1d, Range1d)) and axis_type in ["linear", "datetime", "auto", None]:
          return LinearScale()
      elif isinstance(range_input, (DataRange1d, Range1d)) and axis_type == "log":
          return LogScale()
      elif isinstance(range_input, FactorRange):
          return CategoricalScale()
      else:
          raise ValueError("Unable to determine proper scale for: '%s'" % str(range_input))
  
  
  def _get_axis_class(axis_type, range_input):
      if axis_type is None:
          return None
      elif axis_type == "linear":
          return LinearAxis
      elif axis_type == "log":
          return LogAxis
      elif axis_type == "datetime":
          return DatetimeAxis
      elif axis_type == "auto":
          if isinstance(range_input, FactorRange):
              return CategoricalAxis
          elif isinstance(range_input, Range1d):
              try:
                  # Easier way to validate type of Range1d parameters
                  Datetime.validate(Datetime(), range_input.start)
                  return DatetimeAxis
              except ValueError:
                  pass
          return LinearAxis
      else:
          raise ValueError("Unrecognized axis_type: '%r'" % axis_type)
  
  
  def _get_num_minor_ticks(axis_class, num_minor_ticks):
      if isinstance(num_minor_ticks, int):
          if num_minor_ticks <= 1:
              raise ValueError("num_minor_ticks must be > 1")
          return num_minor_ticks
      if num_minor_ticks is None:
          return 0
      if num_minor_ticks == 'auto':
          if axis_class is LogAxis:
              return 10
          return 5
  
  _known_tools = {
      "pan": lambda: PanTool(dimensions='both'),
      "xpan": lambda: PanTool(dimensions='width'),
      "ypan": lambda: PanTool(dimensions='height'),
      "wheel_zoom": lambda: WheelZoomTool(dimensions='both'),
      "xwheel_zoom": lambda: WheelZoomTool(dimensions='width'),
      "ywheel_zoom": lambda: WheelZoomTool(dimensions='height'),
      "zoom_in": lambda: ZoomInTool(dimensions='both'),
      "xzoom_in": lambda: ZoomInTool(dimensions='width'),
      "yzoom_in": lambda: ZoomInTool(dimensions='height'),
      "zoom_out": lambda: ZoomOutTool(dimensions='both'),
      "xzoom_out": lambda: ZoomOutTool(dimensions='width'),
      "yzoom_out": lambda: ZoomOutTool(dimensions='height'),
      "xwheel_pan": lambda: WheelPanTool(dimension="width"),
      "ywheel_pan": lambda: WheelPanTool(dimension="height"),
      "resize": lambda: ResizeTool(),
      "click": lambda: TapTool(behavior="inspect"),
      "tap": lambda: TapTool(),
      "crosshair": lambda: CrosshairTool(),
      "box_select": lambda: BoxSelectTool(),
      "xbox_select": lambda: BoxSelectTool(dimensions='width'),
      "ybox_select": lambda: BoxSelectTool(dimensions='height'),
      "poly_select": lambda: PolySelectTool(),
      "lasso_select": lambda: LassoSelectTool(),
      "box_zoom": lambda: BoxZoomTool(dimensions='both'),
      "xbox_zoom": lambda: BoxZoomTool(dimensions='width'),
      "ybox_zoom": lambda: BoxZoomTool(dimensions='height'),
      "hover": lambda: HoverTool(tooltips=[
          ("index", "$index"),
          ("data (x, y)", "($x, $y)"),
          ("canvas (x, y)", "($sx, $sy)"),
      ]),
      "save": lambda: SaveTool(),
      "previewsave": "save",
      "undo": lambda: UndoTool(),
      "redo": lambda: RedoTool(),
      "reset": lambda: ResetTool(),
      "help": lambda: HelpTool(),
  }
  
  
  def _tool_from_string(name):
      """ Takes a string and returns a corresponding `Tool` instance. """
      known_tools = sorted(_known_tools.keys())
  
      if name in known_tools:
          tool_fn = _known_tools[name]
  
          if isinstance(tool_fn, string_types):
              tool_fn = _known_tools[tool_fn]
  
          return tool_fn()
      else:
          matches, text = difflib.get_close_matches(name.lower(), known_tools), "similar"
  
          if not matches:
              matches, text = known_tools, "possible"
  
          raise ValueError("unexpected tool name '%s', %s tools are %s" % (name, text, nice_join(matches)))
  
  
  def _process_axis_and_grid(plot, axis_type, axis_location, minor_ticks, axis_label, rng, dim):
      axiscls = _get_axis_class(axis_type, rng)
      if axiscls:
  
          if axiscls is LogAxis:
              if dim == 0:
                  plot.x_scale = LogScale()
              elif dim == 1:
                  plot.y_scale = LogScale()
              else:
                  raise ValueError("received invalid dimension value: %r" % dim)
  
          # this is so we can get a ticker off the axis, even if we discard it
          axis = axiscls(plot=plot if axis_location else None)
  
          if isinstance(axis.ticker, ContinuousTicker):
              axis.ticker.num_minor_ticks = _get_num_minor_ticks(axiscls, minor_ticks)
  
          axis_label = axis_label
          if axis_label:
              axis.axis_label = axis_label
  
          grid = Grid(plot=plot, dimension=dim, ticker=axis.ticker); grid
  
          if axis_location is not None:
              getattr(plot, axis_location).append(axis)
  
  
  def _process_tools_arg(plot, tools):
      """ Adds tools to the plot object
  
      Args:
          plot (Plot): instance of a plot object
          tools (seq[Tool or str]|str): list of tool types or string listing the
              tool names. Those are converted using the _tool_from_string
              function. I.e.: `wheel_zoom,box_zoom,reset`.
  
      Returns:
          list of Tools objects added to plot, map of supplied string names to tools
      """
      tool_objs = []
      tool_map = {}
      temp_tool_str = ""
      repeated_tools = []
  
      if isinstance(tools, (list, tuple)):
          for tool in tools:
              if isinstance(tool, Tool):
                  tool_objs.append(tool)
              elif isinstance(tool, string_types):
                  temp_tool_str += tool + ','
              else:
                  raise ValueError("tool should be a string or an instance of Tool class")
          tools = temp_tool_str
  
      for tool in re.split(r"\s*,\s*", tools.strip()):
          # re.split will return empty strings; ignore them.
          if tool == "":
              continue
  
          tool_obj = _tool_from_string(tool)
          tool_objs.append(tool_obj)
          tool_map[tool] = tool_obj
  
      for typename, group in itertools.groupby(
              sorted([tool.__class__.__name__ for tool in tool_objs])):
          if len(list(group)) > 1:
              repeated_tools.append(typename)
  
      if repeated_tools:
          warnings.warn("%s are being repeated" % ",".join(repeated_tools))
  
      return tool_objs, tool_map
  
  
  def _process_active_tools(toolbar, tool_map, active_drag, active_inspect, active_scroll, active_tap):
      """ Adds tools to the plot object
  
      Args:
          toolbar (Toolbar): instance of a Toolbar object
          tools_map (dict[str]|Tool): tool_map from _process_tools_arg
          active_drag (str or Tool): the tool to set active for drag
          active_inspect (str or Tool): the tool to set active for inspect
          active_scroll (str or Tool): the tool to set active for scroll
          active_tap (str or Tool): the tool to set active for tap
  
      Returns:
          None
  
      Note:
          This function sets properties on Toolbar
      """
      if active_drag in ['auto', None] or isinstance(active_drag, Tool):
          toolbar.active_drag = active_drag
      elif active_drag in tool_map:
          toolbar.active_drag = tool_map[active_drag]
      else:
          raise ValueError("Got unknown %r for 'active_drag', which was not a string supplied in 'tools' argument" % active_drag)
  
      if active_inspect in ['auto', None] or isinstance(active_inspect, Tool) or all([isinstance(t, Tool) for t in active_inspect]):
          toolbar.active_inspect = active_inspect
      elif active_inspect in tool_map:
          toolbar.active_inspect = tool_map[active_inspect]
      else:
          raise ValueError("Got unknown %r for 'active_inspect', which was not a string supplied in 'tools' argument" % active_scroll)
  
      if active_scroll in ['auto', None] or isinstance(active_scroll, Tool):
          toolbar.active_scroll = active_scroll
      elif active_scroll in tool_map:
          toolbar.active_scroll = tool_map[active_scroll]
      else:
          raise ValueError("Got unknown %r for 'active_scroll', which was not a string supplied in 'tools' argument" % active_scroll)
  
      if active_tap in ['auto', None] or isinstance(active_tap, Tool):
          toolbar.active_tap = active_tap
      elif active_tap in tool_map:
          toolbar.active_tap = tool_map[active_tap]
      else:
          raise ValueError("Got unknown %r for 'active_tap', which was not a string supplied in 'tools' argument" % active_tap)
  
  def _get_argspecs(glyphclass):
      argspecs = OrderedDict()
      for arg in glyphclass._args:
          spec = {}
          descriptor = getattr(glyphclass, arg)
  
          # running python with -OO will discard docstrings -> __doc__ is None
          if descriptor.__doc__:
              spec['desc'] = "\n        ".join(textwrap.dedent(descriptor.__doc__).split("\n"))
          else:
              spec['desc'] = ""
          spec['default'] = descriptor.class_default(glyphclass)
          spec['type'] = descriptor.property._sphinx_type()
          argspecs[arg] = spec
      return argspecs
  
  # This template generates the following:
  #
  # def foo(self, x, y=10, kwargs):
  #     kwargs['x'] = x
  #     kwargs['y'] = y
  #     return func(self, **kwargs)
  _sigfunc_template = """
  def %s(self, %s, **kwargs):
  %s
      return func(self, **kwargs)
  """
  
  def _get_sigfunc(func_name, func, argspecs):
      # This code is to wrap the generic func(*args, **kw) glyph method so that
      # a much better signature is available to users. E.g., for ``square`` we have:
      #
      # Signature: p.square(x, y, size=4, angle=0.0, **kwargs)
      #
      # which provides descriptive names for positional args, as well as any defaults
      func_args_with_defaults = []
      for arg, spec in argspecs.items():
          if spec['default'] is None:
              func_args_with_defaults.append(arg)
          else:
              func_args_with_defaults.append("%s=%r" % (arg, spec['default']))
      args_text = ", ".join(func_args_with_defaults)
      kwargs_assign_text = "\n".join("    kwargs[%r] = %s" % (x, x) for x in argspecs)
      func_text = _sigfunc_template % (func_name, args_text, kwargs_assign_text)
      func_code = compile(func_text, "fakesource", "exec")
      func_globals = {}
      eval(func_code, {"func": func}, func_globals)
      return func_globals[func_name]
  
  _arg_template = """    %s (%s) : %s
          (default: %r)
  """
  _doc_template = """ Configure and add %s glyphs to this Figure.
  
  Args:
  %s
  
  Keyword Args:
  %s
  
  Other Parameters:
      alpha (float) : an alias to set all alpha keyword args at once
      color (Color) : an alias to set all color keyword args at once
      source (ColumnDataSource) : a user supplied data source
      legend (str) : a legend tag for this glyph
      x_range_name (str) : name an extra range to use for mapping x-coordinates
      y_range_name (str) : name an extra range to use for mapping y-coordinates
      level (Enum) : control the render level order for this glyph
  
  It is also possible to set the color and alpha parameters of a "nonselection"
  glyph. To do so, prefix any visual parameter with ``'nonselection_'``.
  For example, pass ``nonselection_alpha`` or ``nonselection_fill_alpha``.
  
  Returns:
      GlyphRenderer
  """
  
  def _add_sigfunc_info(func, argspecs, glyphclass, extra_docs):
      func.__name__ = glyphclass.__name__.lower()
  
      omissions = {'js_event_callbacks', 'js_property_callbacks', 'subscribed_events'}
  
      kwlines = []
      kws = glyphclass.properties() - set(argspecs)
      for kw in kws:
          # these are not really useful, and should also really be private, just skip them
          if kw in omissions: continue
  
          descriptor = getattr(glyphclass, kw)
          typ = descriptor.property._sphinx_type()
          if descriptor.__doc__:
              desc = "\n        ".join(textwrap.dedent(descriptor.__doc__).split("\n"))
          else:
              desc = ""
          kwlines.append(_arg_template % (kw, typ, desc, descriptor.class_default(glyphclass)))
      extra_kws = getattr(glyphclass, '_extra_kws', {})
      for kw, (typ, desc) in extra_kws.items():
          kwlines.append("    %s (%s) : %s" % (kw, typ, desc))
      kwlines.sort()
  
      arglines = []
      for arg, spec in argspecs.items():
          arglines.append(_arg_template % (arg, spec['type'], spec['desc'], spec['default']))
  
      func.__doc__ = _doc_template % (func.__name__, "\n".join(arglines), "\n".join(kwlines))
      if extra_docs:
          func.__doc__ += extra_docs
  
  def _glyph_function(glyphclass, extra_docs=None):
  
      def func(self, **kwargs):
  
          # Process legend kwargs and remove legend before we get going
          legend_item_label = _get_legend_item_label(kwargs)
  
          # Need to check if user source is present before _pop_renderer_args
          is_user_source = kwargs.get('source', None) is not None
          renderer_kws = _pop_renderer_args(kwargs)
          source = renderer_kws['data_source']
          if not isinstance(source, ColumnarDataSource):
              try:
                  # try converting the soruce to ColumnDataSource
                  source = ColumnDataSource(source)
              except ValueError as err:
                  msg = "Failed to auto-convert {curr_type} to ColumnDataSource.\n Original error: {err}".format(
                      curr_type=str(type(source)),
                      err=err.message
                  )
                  reraise(ValueError, ValueError(msg), sys.exc_info()[2])
  
              # update reddered_kws so that others can use the new source
              renderer_kws['data_source'] = source
  
          # handle the main glyph, need to process literals
          glyph_ca = _pop_colors_and_alpha(glyphclass, kwargs)
          _process_sequence_literals(glyphclass, kwargs, source, is_user_source)
          _process_sequence_literals(glyphclass, glyph_ca, source, is_user_source)
  
          # handle the nonselection glyph, we always set one
          nsglyph_ca = _pop_colors_and_alpha(glyphclass, kwargs, prefix='nonselection_', default_alpha=0.1)
  
          # handle the selection glyph, if any properties were given
          if any(x.startswith('selection_') for x in kwargs):
              sglyph_ca = _pop_colors_and_alpha(glyphclass, kwargs, prefix='selection_')
          else:
              sglyph_ca = None
  
          # handle the hover glyph, if any properties were given
          if any(x.startswith('hover_') for x in kwargs):
              hglyph_ca = _pop_colors_and_alpha(glyphclass, kwargs, prefix='hover_')
          else:
              hglyph_ca = None
  
          # handle the mute glyph, if any properties were given
          if any(x.startswith('muted_') for x in kwargs):
              mglyph_ca = _pop_colors_and_alpha(glyphclass, kwargs, prefix='muted_')
          else:
              mglyph_ca = None
  
          glyph = _make_glyph(glyphclass, kwargs, glyph_ca)
          nsglyph = _make_glyph(glyphclass, kwargs, nsglyph_ca)
          sglyph = _make_glyph(glyphclass, kwargs, sglyph_ca)
          hglyph = _make_glyph(glyphclass, kwargs, hglyph_ca)
          mglyph = _make_glyph(glyphclass, kwargs, mglyph_ca)
  
          glyph_renderer = GlyphRenderer(glyph=glyph,
                                         nonselection_glyph=nsglyph,
                                         selection_glyph=sglyph,
                                         hover_glyph=hglyph,
                                         muted_glyph=mglyph,
                                         **renderer_kws)
  
          if legend_item_label:
              _update_legend(self, legend_item_label, glyph_renderer)
  
          for tool in self.select(type=BoxSelectTool):
              tool.renderers.append(glyph_renderer)
  
          self.renderers.append(glyph_renderer)
  
          return glyph_renderer
  
      argspecs = _get_argspecs(glyphclass)
  
      sigfunc = _get_sigfunc(glyphclass.__name__.lower(), func, argspecs)
  
      sigfunc.glyph_method = True
  
      _add_sigfunc_info(sigfunc, argspecs, glyphclass, extra_docs)
  
      return sigfunc
  