createDashboard = function (selector, vizJSON, opts, callback) {
  var dashboardEl = document.querySelector(selector);
  if (!dashboardEl) throw new Error('no element found with selector ' + selector);

  // Default options
  opts = opts || {};
  opts.renderMenu = _.isBoolean(opts.renderMenu)
    ? opts.renderMenu
    : true;
  opts.autoStyle = _.isBoolean(opts.autoStyle)
    ? opts.autoStyle
    : false;

  var widgets = new WidgetsCollection();

  var model = new cdb.core.Model({
    title: vizJSON.title,
    description: vizJSON.description,
    updatedAt: vizJSON.updated_at,
    userName: vizJSON.user.fullname,
    userProfileURL: vizJSON.user.profile_url,
    userAvatarURL: vizJSON.user.avatar_url,
    renderMenu: opts.renderMenu,
    autoStyle: opts.autoStyle,
    showLogo: opts.cartodb_logo,
    initialPosition: {
      bounds: vizJSON.bounds
    }
  });
  var dashboardView = new DashboardView({
    el: dashboardEl,
    widgets: widgets,
    model: model
  });
  var dashboardState = opts.state || URLHelper.getStateFromCurrentURL();
  if (dashboardState && !_.isEmpty(dashboardState.map)) {
    if (_.isArray(dashboardState.map.center)) {
      vizJSON.center = dashboardState.map.center;
    }
    if (_.isNumber(dashboardState.map.zoom)) {
      vizJSON.zoom = dashboardState.map.zoom;
    }
    if (dashboardState.map.ne && dashboardState.map.sw) {
      vizJSON.bounds = [dashboardState.map.ne, dashboardState.map.sw];
    }
  }

  var vis = cdb.createVis(dashboardView.$('#map'), vizJSON, _.extend(opts, {
    skipMapInstantiation: true
  }));

  vis.once('load', function (vis) {
    var widgetsState = (dashboardState && dashboardState.widgets) || {};

    // Create widgets
    var widgetsService = new WidgetsService(widgets, vis.dataviews);

    var widgetModelsMap = {
      formula: widgetsService.createFormulaModel.bind(widgetsService),
      histogram: widgetsService.createHistogramModel.bind(widgetsService),
      'time-series': widgetsService.createTimeSeriesModel.bind(widgetsService),
      category: widgetsService.createCategoryModel.bind(widgetsService)
    };
    vizJSON.widgets.forEach(function (widget) {
      // Flatten the data structure given in vizJSON, the widgetsService will use whatever it needs and ignore the rest
      var attrs = _.extend({}, widget, widget.options);
      var newWidgetModel = widgetModelsMap[widget.type];
      var state = widgetsState[widget.id];

      if (_.isFunction(newWidgetModel)) {
        // Find the Layer that the Widget should be created for.
        var layer;
        var source;
        if (widget.layer_id) {
          layer = vis.map.layers.get(widget.layer_id);
        } else if (Number.isInteger(widget.layerIndex)) {
          // TODO Since namedmap doesn't have ids we need to map in another way, here using index
          //   should we solve this in another way?
          layer = vis.map.layers.at(widget.layerIndex);
        }
        if (widget.source && widget.source.id) {
          source = vis.analysis.findNodeById(widget.source.id);
          attrs.source = source;
        }

        newWidgetModel(attrs, layer, state);
      } else {
        cdb.log.error('No widget found for type ' + widget.type);
      }
    });

    dashboardView.render();

    var callbackObj = {
      dashboardView: dashboardView,
      widgets: widgetsService,
      areWidgetsInitialised: function () {
        var widgetsCollection = widgetsService.getCollection();
        if (widgetsCollection.size() > 0) {
          return widgetsCollection.hasInitialState();
        }
        return true;
      },
      vis: vis
    };

    vis.instantiateMap({
      success: function () {
        callback && callback(null, callbackObj);
      },
      error: function (errorMessage) {
        callback && callback(new Error(errorMessage), callbackObj);
      }
    });
  });
}
Translates a vizJSON v3 datastructure into a working dashboard which will be rendered in given selector.

@param {String} selector e.g. "#foobar-id", ".some-class"
@param {Object} vizJSON JSON datastructure
@param {Object} opts (Optional) flags, see 3rd param for cdb.createVis for available ones. Keys used here:
  renderMenu: {Boolean} If true (default), render a top-level menu on the left side.
@return {Object} with keys:
  dashboardView: root (backbone) view of the dashboard
  vis: the instantiated vis map, same result as given from cdb.createVis()