docs: def chart_html(driver_name, chart_type, source, chart_options=None, fields='*', refresh_interval=0,
                 refresh_data=None, control_defaults=None, control_ids=None, schema=None):
    """ Return HTML for a chart.
  
    Args:
      driver_name: the name of the chart driver. Currently we support 'plotly' or 'gcharts'.
      chart_type: string specifying type of chart.
      source: the data source for the chart. Can be actual data (e.g. list) or the name of
          a data source (e.g. the name of a query module).
      chart_options: a dictionary of options for the chart. Can contain a 'controls' entry
          specifying controls. Other entries are passed as JSON to Google Charts.
      fields: the fields to chart. Can be '*' for all fields (only sensible if the columns are
          ordered; e.g. a Query or list of lists, but not a list of dictionaries); otherwise a
          string containing a comma-separated list of field names.
      refresh_interval: a time in seconds after which the chart data will be refreshed. 0 if the
          chart should not be refreshed (i.e. the data is static).
      refresh_data: if the source is a list or other raw data, this is a YAML string containing
          metadata needed to support calls to refresh (get_chart_data).
      control_defaults: the default variable values for controls that are shared across charts
          including this one.
      control_ids: the DIV IDs for controls that are shared across charts including this one.
      schema: an optional schema for the data; if not supplied one will be inferred.
  
    Returns:
      A string containing the HTML for the chart.
  
    """
    div_id = _html.Html.next_id()
    controls_html = ''
    if control_defaults is None:
      control_defaults = {}
    if control_ids is None:
      control_ids = []
    if chart_options is not None and 'variables' in chart_options:
      controls = chart_options['variables']
      del chart_options['variables']  # Just to make sure GCharts doesn't see them.
      controls_html, defaults, ids = parse_control_options(controls)
      # We augment what we are passed so that in principle we can have controls that are
      # shared by charts as well as controls that are specific to a chart.
      control_defaults.update(defaults)
      control_ids.extend(ids),
  
    _HTML_TEMPLATE = """
      <div class="bqgc-container">
        {controls}
        <div class="bqgc {extra_class}" id="{id}">
        </div>
      </div>
      <script src="/static/components/requirejs/require.js"></script>
      <script>
        require.config({{
          paths: {{
            base: '/static/base',
            d3: '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3',
            plotly: 'https://cdn.plot.ly/plotly-1.5.1.min.js?noext',
            jquery: '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min'
          }},
          map: {{
            '*': {{
              datalab: 'nbextensions/gcpdatalab'
            }}
          }},
          shim: {{
            plotly: {{
              deps: ['d3', 'jquery'],
              exports: 'plotly'
            }}
          }}
        }});
  
        require(['datalab/charting',
                 'datalab/element!{id}',
                 'base/js/events',
                 'datalab/style!/nbextensions/gcpdatalab/charting.css'
                ],
          function(charts, dom, events) {{
            charts.render(
                '{driver}',
                dom,
                events,
                '{chart_type}',
                {control_ids},
                {data},
                {options},
                {refresh_data},
                {refresh_interval},
                {total_rows});
            }}
          );
      </script>
    """
    count = 25 if chart_type == 'paged_table' else -1
    data, total_count = get_data(source, fields, control_defaults, 0, count, schema)
    if refresh_data is None:
      if isinstance(source, basestring):
        source_index = get_data_source_index(source)
        refresh_data = {'source_index': source_index, 'name': source_index}
      else:
        refresh_data = {'name': 'raw data'}
    refresh_data['fields'] = fields
  
    # TODO(gram): check if we need to augment env with user_ns
    return _HTML_TEMPLATE \
        .format(driver=driver_name,
                controls=controls_html,
                id=div_id,
                chart_type=chart_type,
                extra_class=" bqgc-controlled" if len(controls_html) else '',
                data=json.dumps(data, cls=google.datalab.utils.JSONEncoder),
                options=json.dumps(chart_options, cls=google.datalab.utils.JSONEncoder),
                refresh_data=json.dumps(refresh_data, cls=google.datalab.utils.JSONEncoder),
                refresh_interval=refresh_interval,
                control_ids=str(control_ids),
                total_rows=total_count)