Initial commit
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
var _ = require('underscore');
|
||||
var cdb = require('internal-carto.js');
|
||||
var Dashboard = require('deep-insights/api/dashboard.js');
|
||||
var DashboardView = require('deep-insights/dashboard-view');
|
||||
var WidgetsCollection = require('deep-insights/widgets/widgets-collection');
|
||||
var WidgetsService = require('deep-insights/widgets-service');
|
||||
var URLHelper = require('deep-insights/api/url-helper');
|
||||
|
||||
/**
|
||||
* 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()
|
||||
*/
|
||||
var 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (selector, vizJSON, opts, callback) {
|
||||
var args = arguments;
|
||||
var fn = args[args.length - 1];
|
||||
|
||||
if (_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
|
||||
function _load (vizJSON) {
|
||||
createDashboard(selector, vizJSON, opts, function (error, dashboard) {
|
||||
var _dashboard = new Dashboard(dashboard);
|
||||
|
||||
if (opts.share_urls) {
|
||||
_dashboard.onStateChanged(
|
||||
_.debounce(
|
||||
function (state, url) {
|
||||
window.history.replaceState('Object', 'Title', url);
|
||||
},
|
||||
500
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
callback && callback(error, _dashboard);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof vizJSON === 'string') {
|
||||
cdb.core.Loader.get(vizJSON, function (data) {
|
||||
if (data) {
|
||||
_load(data, opts);
|
||||
} else {
|
||||
callback && callback(new Error('Error fetching viz.json file: ' + vizJSON));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_load(vizJSON, opts);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
var URLHelper = require('./url-helper');
|
||||
var _ = require('underscore');
|
||||
|
||||
function Dashboard (dashboard) {
|
||||
this._dashboard = dashboard;
|
||||
|
||||
this.onDataviewsFetched(function () {
|
||||
dashboard.widgets._widgetsCollection.initialState();
|
||||
dashboard.widgets._widgetsCollection.each(function (m) {
|
||||
m && m.applyInitialState && m.applyInitialState();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Dashboard.prototype = {
|
||||
|
||||
/**
|
||||
* @return {View} used in the dashboard
|
||||
*/
|
||||
getView: function () {
|
||||
return this._dashboard.dashboardView;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Map} the map used in the dashboard
|
||||
*/
|
||||
getMap: function () {
|
||||
return this._dashboard.vis;
|
||||
},
|
||||
|
||||
reloadMap: function () {
|
||||
this.getMap().reload();
|
||||
},
|
||||
|
||||
forceResize: function () {
|
||||
this._dashboard.widgets.getCollection()
|
||||
.each(function (widget) {
|
||||
widget.forceResize();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Array} of widgets in the dashboard
|
||||
*/
|
||||
getWidgets: function () {
|
||||
return this._dashboard.widgets.getList();
|
||||
},
|
||||
|
||||
getDashboardURL: function () {
|
||||
return URLHelper.getURLFromState(this.getState());
|
||||
},
|
||||
|
||||
getState: function () {
|
||||
var state = {};
|
||||
var mapState = this.getMapState();
|
||||
if (!_.isEmpty(mapState)) state.map = mapState;
|
||||
|
||||
var widgetsCollection = this._dashboard.widgets.getCollection();
|
||||
var widgetsState = widgetsCollection.getStates();
|
||||
if (!_.isEmpty(widgetsState)) state.widgets = widgetsState;
|
||||
return state;
|
||||
},
|
||||
|
||||
getMapState: function () {
|
||||
var map = this._dashboard.vis.map;
|
||||
var currentBoundingBox = map.getViewBounds();
|
||||
var center = map.get('center');
|
||||
var zoom = map.get('zoom');
|
||||
|
||||
return {
|
||||
ne: currentBoundingBox[0],
|
||||
sw: currentBoundingBox[1],
|
||||
center: center,
|
||||
zoom: zoom
|
||||
};
|
||||
},
|
||||
|
||||
setState: function (state) {
|
||||
// todo: set map state
|
||||
this._dashboard.widgets.setWidgetsState(state.widgets);
|
||||
this._dashboard.vis.mapvis.map.setBounds([state.map.ne, state.map.sw]);
|
||||
},
|
||||
|
||||
onDataviewsFetched: function (callback) {
|
||||
var areWidgetsInitialized = this._dashboard.areWidgetsInitialised();
|
||||
if (areWidgetsInitialized) {
|
||||
callback && callback();
|
||||
} else {
|
||||
this._dashboard.vis.once('dataviewsFetched', function () {
|
||||
callback && callback();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onStateChanged: function (callback) {
|
||||
this.onDataviewsFetched(function () {
|
||||
callback && this._bindChange(callback);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_bindChange: function (callback) {
|
||||
var widgetsCollection = this._dashboard.widgets.getCollection();
|
||||
widgetsCollection.bind('change', function () {
|
||||
callback(this.getState(), this.getDashboardURL());
|
||||
}, this);
|
||||
|
||||
this._dashboard.vis.map.bind('change', function () {
|
||||
callback(this.getState(), this.getDashboardURL());
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Integer} id - widget id
|
||||
* @return a widget object
|
||||
*/
|
||||
getWidget: function (id) {
|
||||
return this._dashboard.widgets.get(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a category widget.
|
||||
* @param {Object} widgetAttrs - attributes for the new widget
|
||||
* @param {string} widgetAttrs.id - id (required)
|
||||
* @param {string} widgetAttrs.title - title (required)
|
||||
* @param {number} widgetAttrs.order - index of the widget (optional)
|
||||
* @param ...
|
||||
* @return {CategoryWidget} The new widget
|
||||
*/
|
||||
createCategoryWidget: function (widgetAttrs, layer) {
|
||||
return this._dashboard.widgets.createCategoryModel(widgetAttrs, layer, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a histogram widget
|
||||
* @param {Object} widgetAttrs - attributes for the new widget
|
||||
* @param {string} widgetAttrs.id - id (required)
|
||||
* @param {string} widgetAttrs.title - title (required)
|
||||
* @param {number} widgetAttrs.order - index of the widget (optional)
|
||||
* @param ...
|
||||
* @return {HistogramWidget} The new widget
|
||||
*/
|
||||
createHistogramWidget: function (widgetAttrs, layer) {
|
||||
return this._dashboard.widgets.createHistogramModel(widgetAttrs, layer, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a formula widget
|
||||
* @param {Object} widgetAttrs - attributes for the new widget
|
||||
* @param {string} widgetAttrs.id - id (required)
|
||||
* @param {string} widgetAttrs.title - title (required)
|
||||
* @param {number} widgetAttrs.order - index of the widget (optional)
|
||||
* @param ...
|
||||
* @return {FormulaWidget} The new widget
|
||||
*/
|
||||
createFormulaWidget: function (widgetAttrs, layer) {
|
||||
return this._dashboard.widgets.createFormulaModel(widgetAttrs, layer, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a timesier es widget
|
||||
* @param {Object} widgetAttrs - attributes for the new widget
|
||||
* @param {string} widgetAttrs.id - id (required)
|
||||
* @param {string} widgetAttrs.title - title (required)
|
||||
* @param {number} widgetAttrs.order - index of the widget (optional)
|
||||
* @param ...
|
||||
* @return {TimeSeriesWidget} The new widget
|
||||
*/
|
||||
createTimeSeriesWidget: function (widgetAttrs, layer) {
|
||||
return this._dashboard.widgets.createTimeSeriesModel(widgetAttrs, layer, null);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Dashboard;
|
||||
@@ -0,0 +1,31 @@
|
||||
var URI = require('urijs');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
getURLFromState: function (state) {
|
||||
var thisURL = new URI(this.getLocalURL());
|
||||
thisURL.removeQuery('state');
|
||||
if (!_.isEmpty(state)) {
|
||||
var statesString = JSON.stringify(state);
|
||||
thisURL.setQuery('state', statesString);
|
||||
}
|
||||
return thisURL.toString();
|
||||
},
|
||||
getStateFromCurrentURL: function () {
|
||||
var currentURL = this.getLocalURL();
|
||||
return this.getStateFromURL(currentURL);
|
||||
},
|
||||
getStateFromURL: function (url) {
|
||||
var uri = new URI(url);
|
||||
var state;
|
||||
var result = {};
|
||||
if (uri.hasQuery('state')) {
|
||||
state = uri.query(true)['state'];
|
||||
if (!_.isEmpty(state)) {
|
||||
result = JSON.parse(decodeURIComponent(state));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
getLocalURL: _.constant(window.location.href)
|
||||
};
|
||||
Reference in New Issue
Block a user