globe.gl
Version:
UI component for Globe Data Visualization using ThreeJS/WebGL
607 lines (531 loc) • 20.2 kB
JavaScript
;
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var three$1 = require('three');
var ThreeGlobe = _interopDefault(require('three-globe'));
var ThreeRenderObjects = _interopDefault(require('three-render-objects'));
var accessorFn = _interopDefault(require('accessor-fn'));
var Kapsule = _interopDefault(require('kapsule'));
var TWEEN = _interopDefault(require('@tweenjs/tween.js'));
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(n);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function linkKapsule (kapsulePropName, kapsuleType) {
var dummyK = new kapsuleType(); // To extract defaults
return {
linkProp: function linkProp(prop) {
// link property config
return {
"default": dummyK[prop](),
onChange: function onChange(v, state) {
state[kapsulePropName][prop](v);
},
triggerUpdate: false
};
},
linkMethod: function linkMethod(method) {
// link method pass-through
return function (state) {
var kapsuleInstance = state[kapsulePropName];
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var returnVal = kapsuleInstance[method].apply(kapsuleInstance, args);
return returnVal === kapsuleInstance ? this // chain based on the parent object, not the inner kapsule
: returnVal;
};
}
};
}
var three = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
AmbientLight: three$1.AmbientLight,
DirectionalLight: three$1.DirectionalLight,
Vector2: three$1.Vector2
};
// Expose config from ThreeGlobe
var bindGlobe = linkKapsule('globe', ThreeGlobe);
var linkedGlobeProps = Object.assign.apply(Object, _toConsumableArray(['globeImageUrl', 'bumpImageUrl', 'showAtmosphere', 'showGraticules', 'pointsData', 'pointLat', 'pointLng', 'pointColor', 'pointAltitude', 'pointRadius', 'pointResolution', 'pointsMerge', 'pointsTransitionDuration', 'arcsData', 'arcStartLat', 'arcStartLng', 'arcEndLat', 'arcEndLng', 'arcColor', 'arcAltitude', 'arcAltitudeAutoScale', 'arcStroke', 'arcCurveResolution', 'arcCircularResolution', 'arcDashLength', 'arcDashGap', 'arcDashInitialGap', 'arcDashAnimateTime', 'arcsTransitionDuration', 'polygonsData', 'polygonGeoJsonGeometry', 'polygonCapColor', 'polygonSideColor', 'polygonStrokeColor', 'polygonAltitude', 'polygonsTransitionDuration', 'pathsData', 'pathPoints', 'pathPointLat', 'pathPointLng', 'pathPointAlt', 'pathResolution', 'pathColor', 'pathStroke', 'pathDashLength', 'pathDashGap', 'pathDashInitialGap', 'pathDashAnimateTime', 'pathTransitionDuration', 'hexBinPointsData', 'hexBinPointLat', 'hexBinPointLng', 'hexBinPointWeight', 'hexBinResolution', 'hexMargin', 'hexTopColor', 'hexSideColor', 'hexAltitude', 'hexBinMerge', 'hexTransitionDuration', 'hexPolygonsData', 'hexPolygonGeoJsonGeometry', 'hexPolygonColor', 'hexPolygonAltitude', 'hexPolygonResolution', 'hexPolygonMargin', 'hexPolygonsTransitionDuration', 'labelsData', 'labelLat', 'labelLng', 'labelAltitude', 'labelRotation', 'labelText', 'labelSize', 'labelTypeFace', 'labelColor', 'labelResolution', 'labelIncludeDot', 'labelDotRadius', 'labelDotOrientation', 'labelsTransitionDuration', 'customLayerData', 'customThreeObject', 'customThreeObjectUpdate'].map(function (p) {
return _defineProperty({}, p, bindGlobe.linkProp(p));
})));
var linkedGlobeMethods = Object.assign.apply(Object, _toConsumableArray(['globeMaterial', 'getCoords', 'toGeoCoords'].map(function (p) {
return _defineProperty({}, p, bindGlobe.linkMethod(p));
}))); // Expose config from renderObjs
var bindRenderObjs = linkKapsule('renderObjs', ThreeRenderObjects);
var linkedRenderObjsProps = Object.assign.apply(Object, _toConsumableArray(['width', 'height', 'backgroundColor', 'backgroundImageUrl', 'enablePointerInteraction'].map(function (p) {
return _defineProperty({}, p, bindRenderObjs.linkProp(p));
}))); //
var GLOBE_RADIUS = 100;
var globe = Kapsule({
props: _objectSpread2({
onZoom: {
triggerUpdate: false
},
pointLabel: {
"default": 'name',
triggerUpdate: false
},
onPointClick: {
"default": function _default() {},
triggerUpdate: false
},
onPointRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onPointHover: {
"default": function _default() {},
triggerUpdate: false
},
arcLabel: {
"default": 'name',
triggerUpdate: false
},
onArcClick: {
"default": function _default() {},
triggerUpdate: false
},
onArcRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onArcHover: {
"default": function _default() {},
triggerUpdate: false
},
polygonLabel: {
"default": 'name',
triggerUpdate: false
},
onPolygonClick: {
"default": function _default() {},
triggerUpdate: false
},
onPolygonRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onPolygonHover: {
"default": function _default() {},
triggerUpdate: false
},
pathLabel: {
"default": 'name',
triggerUpdate: false
},
onPathClick: {
"default": function _default() {},
triggerUpdate: false
},
onPathRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onPathHover: {
"default": function _default() {},
triggerUpdate: false
},
hexLabel: {
triggerUpdate: false
},
onHexClick: {
"default": function _default() {},
triggerUpdate: false
},
onHexRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onHexHover: {
"default": function _default() {},
triggerUpdate: false
},
hexPolygonLabel: {
triggerUpdate: false
},
onHexPolygonClick: {
"default": function _default() {},
triggerUpdate: false
},
onHexPolygonRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onHexPolygonHover: {
"default": function _default() {},
triggerUpdate: false
},
labelLabel: {
triggerUpdate: false
},
onLabelClick: {
"default": function _default() {},
triggerUpdate: false
},
onLabelRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onLabelHover: {
"default": function _default() {},
triggerUpdate: false
},
customLayerLabel: {
"default": 'name',
triggerUpdate: false
},
onCustomLayerClick: {
"default": function _default() {},
triggerUpdate: false
},
onCustomLayerRightClick: {
"default": function _default() {},
triggerUpdate: false
},
onCustomLayerHover: {
"default": function _default() {},
triggerUpdate: false
}
}, linkedGlobeProps, {}, linkedRenderObjsProps),
methods: _objectSpread2({
pauseAnimation: function pauseAnimation(state) {
if (state.animationFrameRequestId !== null) {
cancelAnimationFrame(state.animationFrameRequestId);
state.animationFrameRequestId = null;
}
return this;
},
resumeAnimation: function resumeAnimation(state) {
if (state.animationFrameRequestId === null) {
this._animationCycle();
}
return this;
},
_animationCycle: function _animationCycle(state) {
// Frame cycle
state.renderObjs.tick();
state.animationFrameRequestId = requestAnimationFrame(this._animationCycle);
},
pointOfView: function pointOfView(state) {
var geoCoords = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var transitionDuration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var curGeoCoords = getGeoCoords(); // Getter
if (geoCoords.lat === undefined && geoCoords.lng === undefined && geoCoords.altitude === undefined) {
return curGeoCoords;
} else {
// Setter
var finalGeoCoords = Object.assign({}, curGeoCoords, geoCoords);
if (!transitionDuration) {
// no animation
setCameraPos(finalGeoCoords);
} else {
// Avoid rotating more than 180deg longitude
while (curGeoCoords.lng - finalGeoCoords.lng > 180) {
curGeoCoords.lng -= 360;
}
while (curGeoCoords.lng - finalGeoCoords.lng < -180) {
curGeoCoords.lng += 360;
}
new TWEEN.Tween(curGeoCoords).to(finalGeoCoords, transitionDuration).easing(TWEEN.Easing.Cubic.InOut).onUpdate(setCameraPos).start();
}
return this;
} //
function getGeoCoords() {
return state.globe.toGeoCoords(state.renderObjs.cameraPosition());
}
function setCameraPos(_ref4) {
var lat = _ref4.lat,
lng = _ref4.lng,
altitude = _ref4.altitude;
state.renderObjs.cameraPosition(state.globe.getCoords(lat, lng, altitude));
}
},
scene: function scene(state) {
return state.renderObjs.scene();
},
// Expose scene
camera: function camera(state) {
return state.renderObjs.camera();
},
// Expose camera
renderer: function renderer(state) {
return state.renderObjs.renderer();
},
// Expose renderer
controls: function controls(state) {
return state.renderObjs.controls();
},
// Expose controls
_destructor: function _destructor() {
this.pauseAnimation();
this.pointsData([]);
this.arcsData([]);
this.polygonsData([]);
this.pathsData([]);
this.hexBinPointsData([]);
this.hexPolygonsData([]);
this.labelsData([]);
this.customLayerData([]);
}
}, linkedGlobeMethods),
stateInit: function stateInit(_ref5) {
var rendererConfig = _ref5.rendererConfig,
_ref5$waitForGlobeRea = _ref5.waitForGlobeReady,
waitForGlobeReady = _ref5$waitForGlobeRea === void 0 ? true : _ref5$waitForGlobeRea,
globeInitConfig = _objectWithoutProperties(_ref5, ["rendererConfig", "waitForGlobeReady"]);
return {
globe: new ThreeGlobe(_objectSpread2({
waitForGlobeReady: waitForGlobeReady
}, globeInitConfig)),
renderObjs: ThreeRenderObjects({
controlType: 'orbit',
rendererConfig: rendererConfig,
waitForLoadComplete: waitForGlobeReady
}).skyRadius(GLOBE_RADIUS * 500).showNavInfo(false)
};
},
init: function init(domNode, state) {
var _this = this;
// Wipe DOM
domNode.innerHTML = ''; // Add relative container
domNode.appendChild(state.container = document.createElement('div'));
state.container.style.position = 'relative'; // Add renderObjs
var roDomNode = document.createElement('div');
state.container.appendChild(roDomNode);
state.renderObjs(roDomNode); // inject renderer size on three-globe
state.globe.rendererSize(state.renderObjs.renderer().getSize(new three.Vector2())); // set initial distance
this.pointOfView({
altitude: 2.5
}); // calibrate orbit controls
var controls = state.renderObjs.controls();
controls.minDistance = GLOBE_RADIUS * 1.01; // just above the surface
setTimeout(function () {
return controls.maxDistance = GLOBE_RADIUS * 100;
}); // apply async after renderObjs sets maxDistance
controls.enablePan = false;
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.rotateSpeed = 0.1;
controls.zoomSpeed = 0.3;
controls.addEventListener('change', function () {
// adjust controls speed based on altitude
var pov = _this.pointOfView();
controls.rotateSpeed = pov.altitude * 0.06; // Math.pow(pov.altitude + 1, 2) * 0.008;
controls.zoomSpeed = (pov.altitude + 1) * 0.1; // Math.sqrt(pov.altitude) * 0.2;
state.onZoom && state.onZoom(pov);
}); // config renderObjs
var getGlobeObj = function getGlobeObj(object) {
var obj = object; // recurse up object chain until finding the globe object
while (obj && !obj.hasOwnProperty('__globeObjType')) {
obj = obj.parent;
}
return obj;
};
var dataAccessors = {
point: function point(d) {
return d;
},
arc: function arc(d) {
return d;
},
polygon: function polygon(d) {
return d.data;
},
path: function path(d) {
return d;
},
hexbin: function hexbin(d) {
return d;
},
hexPolygon: function hexPolygon(d) {
return d;
},
label: function label(d) {
return d;
},
custom: function custom(d) {
return d;
}
};
state.renderObjs.objects([// Populate scene
new three.AmbientLight(0xbbbbbb), new three.DirectionalLight(0xffffff, 0.6), state.globe]).hoverOrderComparator(function (a, b) {
var aObj = getGlobeObj(a);
var bObj = getGlobeObj(b); // de-prioritize background / non-globe objects
var isBackground = function isBackground(o) {
return !o;
}; // || o.__globeObjType === 'globe' || o.__globeObjType === 'atmosphere';
return isBackground(aObj) - isBackground(bObj);
}).lineHoverPrecision(0.2).tooltipContent(function (obj) {
var objAccessors = {
point: state.pointLabel,
arc: state.arcLabel,
polygon: state.polygonLabel,
path: state.pathLabel,
hexbin: state.hexLabel,
hexPolygon: state.hexPolygonLabel,
label: state.labelLabel,
custom: state.customLayerLabel
};
var globeObj = getGlobeObj(obj);
var objType = globeObj.__globeObjType;
return globeObj && objAccessors.hasOwnProperty(objType) && dataAccessors.hasOwnProperty(objType) ? accessorFn(objAccessors[objType])(dataAccessors[objType](globeObj.__data)) || '' : '';
}).onHover(function (obj) {
// Update tooltip and trigger onHover events
var hoverObjFns = {
point: state.onPointHover,
arc: state.onArcHover,
polygon: state.onPolygonHover,
path: state.onPathHover,
hexbin: state.onHexHover,
hexPolygon: state.onHexPolygonHover,
label: state.onLabelHover,
custom: state.onCustomLayerHover
};
var hoverObj = getGlobeObj(obj); // ignore non-recognised obj types
hoverObj && !hoverObjFns.hasOwnProperty(hoverObj.__globeObjType) && (hoverObj = null);
if (hoverObj !== state.hoverObj) {
var prevObjType = state.hoverObj ? state.hoverObj.__globeObjType : null;
var prevObjData = state.hoverObj ? dataAccessors[prevObjType](state.hoverObj.__data) : null;
var objType = hoverObj ? hoverObj.__globeObjType : null;
var objData = hoverObj ? dataAccessors[objType](hoverObj.__data) : null;
if (prevObjType && prevObjType !== objType) {
// Hover out
hoverObjFns[prevObjType](null, prevObjData);
}
if (objType) {
// Hover in
hoverObjFns[objType](objData, prevObjType === objType ? prevObjData : null);
}
state.hoverObj = hoverObj;
}
}).onClick(function (obj) {
if (!obj) return; // ignore background clicks
// Handle click events on objects
var objFns = {
point: state.onPointClick,
arc: state.onArcClick,
polygon: state.onPolygonClick,
path: state.onPathClick,
hexbin: state.onHexClick,
hexPolygon: state.onHexPolygonClick,
label: state.onLabelClick,
custom: state.onCustomLayerClick
};
var globeObj = getGlobeObj(obj);
var objType = globeObj.__globeObjType;
if (globeObj && objFns.hasOwnProperty(objType) && dataAccessors.hasOwnProperty(objType)) {
objFns[objType](dataAccessors[objType](globeObj.__data));
}
}).onRightClick(function (obj) {
if (!obj) return; // ignore background clicks
// Handle right-click events
var objFns = {
point: state.onPointRightClick,
arc: state.onArcRightClick,
polygon: state.onPolygonRightClick,
path: state.onPathRightClick,
hexbin: state.onHexRightClick,
hexPolygon: state.onHexPolygonRightClick,
label: state.onLabelRightClick,
custom: state.onCustomLayerRightClick
};
var globeObj = getGlobeObj(obj);
var objType = globeObj.__globeObjType;
if (globeObj && objFns.hasOwnProperty(objType) && dataAccessors.hasOwnProperty(objType)) {
objFns[objType](dataAccessors[objType](globeObj.__data));
}
}); //
// Kick-off renderer
this._animationCycle();
}
});
module.exports = globe;