/** * Created by vitaliy on 1/17/18. */ import { Format } from '../../../../helpers/format.helper'; import { GeoJSON } from 'leaflet'; //@ts-ignore // var OMS_resource = require('../../../../scripts/compiled/oms.js'); export default function (L) { (function (factory, L) { L.CanvasFlowmapLayer = factory(L); L.canvasFlowmapLayer = function (originAndDestinationGeoJsonPoints, opts) { return new L.CanvasFlowmapLayer(originAndDestinationGeoJsonPoints, opts); } }(function (L) { // layer source code var oms; var canvasRenderer = L.canvas(); var CanvasFlowmapLayer = L.GeoJSON.extend({ options: { // this is only a default option example, // developers will most likely need to provide this // options object with values unique to their data canvasBezierStyle: { type: 'simple', symbol: { // use canvas styling options (compare to CircleMarker styling below) strokeStyle: 'rgba(255, 0, 51, 0.8)', lineWidth: 75, lineCap: 'butt', shadowColor: 'rgb(255, 0, 51)', shadowBlur: 1.5 } }, sameSlices: false, animatedCanvasBezierStyle: { type: 'simple', symbol: { // use canvas styling options (compare to CircleMarker styling below) strokeStyle: 'rgb(255, 46, 88)', lineWidth: 3.25, lineDashOffsetSize: 4, // custom property used with animation sprite sizes lineCap: 'butt', shadowColor: 'rgb(255, 0, 51)', shadowBlur: 0 } }, // valid values: 'selection' or 'all' // use 'all' to display all Bezier paths immediately // use 'selection' if Bezier paths will be drawn with user interactions pathDisplayMode: 'all', wrapAroundCanvas: true, animationStarted: false, mapValueMaxRange: 2000, animationEasingFamily: 'Cubic', animationEasingType: 'In', calculateOriginPieChartByLinkedCharts: true, animationDuration: 2000, pointToLayer: function (geoJsonPoint, latlng) { var marker = L.circleMarker(latlng); if (oms) { oms.addMarker(marker); } return marker; }, style: function (geoJsonFeature) { // use leaflet's path styling options // since the GeoJSON feature properties are modified by the layer, // developers can rely on the "isOrigin" property to set different // symbols for origin vs destination CircleMarker stylings if (geoJsonFeature.properties.isOrigin) { return { renderer: canvasRenderer, // recommended to use L.canvas() radius: 5, weight: 1, color: 'rgb(195, 255, 62)', fillColor: 'rgba(195, 255, 62, 0.6)', fillOpacity: 0.6 }; } else { return { renderer: canvasRenderer, radius: 2.5, weight: 0.25, color: 'rgb(0, 142, 170)', fillColor: 'rgb(0, 142, 170)', fillOpacity: 0.7 }; } }, pieChartsColors: { "inbound": '#FD9107', "outbound": '#00A2EE', "internal": '#BA58E1' }, callQualityColors: [ { max: 0, color: "#002d5b" }, // grey { max: 2.5, color: "#c12f1a" }, //"red": { max: 3.5, color: "#ffb901" }, //"yellow" { max: 4, color: "#34b440" }, //"lightGreen" { max: 5, color: "#269b2b" } //"darkGreen" ], pieChartsDisplayTypes: { "inbound": true, "outbound": true, "internal": true }, pieChartSize: 30, pieChartSliceFont: "Arial", pieChartSliceFontSize: 13, pieChartSliceTinyFontSize: 8 }, _customCanvases: [], initialize: function (geoJson, options) { // same as L.GeoJSON intialize method, but first performs custom GeoJSON // data parsing and reformatting before finally calling L.GeoJSON addData method L.setOptions(this, options); this._animationPropertiesStatic = { offset: 0, resetOffset: 200, repeat: Infinity, yoyo: false }; this._animationPropertiesDynamic = { duration: null, easingInfo: null }; this._layers = {}; this.activePie = null; this.secondaryActivePie = null; this.pointsMap = {}; this.featuresMap = {}; this.pieSliceLabels = []; this.keepCurrentSelection = false; // beginning of customized initialize method if (geoJson) { this.setNewData(geoJson); } // establish animation properties using Tween.js library // currently requires the developer to add it to their own app index.html // TODO: find better way to wrap it up in this layer source code if (window.hasOwnProperty('TWEEN')) { // set this._animationPropertiesDynamic.duration value this.setAnimationDuration(this.options.animationDuration); // set this._animationPropertiesDynamic.easingInfo value this.setAnimationEasing(this.options.animationEasingFamily, this.options.animationEasingType); // initiate the active animation tween // @ts-ignore: Unreachable code error this._animationTween = new TWEEN.Tween(this._animationPropertiesStatic) .to({ offset: this._animationPropertiesStatic.resetOffset }, this._animationPropertiesDynamic.duration) .easing(this._animationPropertiesDynamic.easingInfo.tweenEasingFunction) .repeat(this._animationPropertiesStatic.repeat) .yoyo(this._animationPropertiesStatic.yoyo) .start(); } else { // Tween.js lib isn't available, // ensure that animations aren't attempted at the beginning console.warn('TWEEN is not available. Animations have been disabled.'); this.options.animationStarted = false; } }, setSliceTypesToDraw: function (config) { this.options.pieChartsDisplayTypes = config; this._redrawCanvas(); }, setNewData: function (geoJsonFeatureCollection) { this.clearLayers(); var newPoints = { type: "FeatureCollection", features: [] }; if (geoJsonFeatureCollection.features) { geoJsonFeatureCollection.features.forEach(function (feature, index) { if (feature.type === 'Feature' && feature.geometry && feature.geometry.type === 'Point' && feature.geometry.coordinates[0]) { // origin feature -- modify attribute properties and geometry feature.properties.isOrigin = true; feature.properties.isHovered = false; feature.properties._isSelectedForPathDisplay = true; feature.properties._uniqueId = index + '_origin'; var inbound = 0; var outbound = 0; if (this.options.calculateOriginPieChartByLinkedCharts) { feature.properties.linkedTo.forEach(function (dest) { inbound += dest.inbound; outbound += dest.outbound; }); feature.properties['pieData'] = { inbound: { 'title': 'test 1', 'val': inbound, 'type': 'inbound' }, outbound: { 'title': 'test 1', 'val': outbound, 'type': 'outbound' }, internal: { 'title': 'test 1', 'val': feature.properties.internalCalls, 'type': 'internal' } } } feature.properties._volume = this._getPieChartRadius(feature.properties.pieData); this.pointsMap[feature.properties.origin_id] = feature.geometry.coordinates; this.featuresMap[feature.properties.origin_id] = feature; newPoints.features.push(feature); } }.bind(this), this); // all origin/destination features are available for future internal used // but only a filtered subset of these are drawn on the map //this.pieChartsGeoJsonPoints = this._filterGeoJsonPieChartsToDraw(geoJsonFeatureCollection); this.originAndDestinationGeoJsonPoints = newPoints; var geoJsonPointsToDraw = geoJsonFeatureCollection; this.addData(geoJsonPointsToDraw); } else { // TODO: improved handling of invalid incoming GeoJson FeatureCollection? this.originAndDestinationGeoJsonPoints = null; } this._redrawCanvas(); return this; }, onAdd: function (map) { // call the L.GeoJSON onAdd method, // then continue with custom code L.GeoJSON.prototype.onAdd.call(this, map); // create new canvas element for manually drawing bezier curves this._canvasElement = this._insertCustomCanvasElement(map, this.options); // create new canvas element for optional, animated bezier curves this._animationCanvasElement = this._insertCustomCanvasElement(map, this.options); this._customCanvases = [this._canvasElement, this._animationCanvasElement] // establish custom event listeners this.on('click mouseover mouseout', this._modifyInteractionEvent, this); map.on('move', this._resetCanvas, this); map.on('moveend', this._resetCanvasAndWrapGeoJsonCircleMarkers, this); map.on('resize', this._resizeCanvas, this); if (map.options.zoomAnimation && L.Browser.any3d) { map.on('zoomanim', this._animateZoom, this); } // calculate initial size and position of canvas // and draw its content for the first time this._resizeCanvas(); this._resetCanvasAndWrapGeoJsonCircleMarkers(); }, onRemove: function (map) { // call the L.GeoJSON onRemove method, // then continue with custom code L.GeoJSON.prototype.onRemove.call(this, map); this._clearCanvas(); this._customCanvases.forEach(function (canvas) { L.DomUtil.remove(canvas); }); // remove custom event listeners this.off('click mouseover mouseout', this._modifyInteractionEvent, this); map.off('move', this._resetCanvas, this); map.off('moveend', this._resetCanvasAndWrapGeoJsonCircleMarkers, this); map.off('resize', this._resizeCanvas, this); if (map.options.zoomAnimation) { map.off('zoomanim', this._animateZoom, this); } }, setAnimationDuration: function (milliseconds) { milliseconds = Number(milliseconds) || this.options.animationDuration; // change the tween duration on the active animation tween if (this._animationTween) { this._animationTween.to({ offset: this._animationPropertiesStatic.resetOffset }, milliseconds); } this._animationPropertiesDynamic.duration = milliseconds; }, setAnimationEasing: function (easingFamily, easingType) { var tweenEasingFunction; if ( // @ts-ignore: Unreachable code error TWEEN.Easing.hasOwnProperty(easingFamily) && TWEEN.Easing[easingFamily].hasOwnProperty(easingType) ) { // @ts-ignore: Unreachable code error tweenEasingFunction = TWEEN.Easing[easingFamily][easingType]; } else { easingFamily = this.options.animationEasingFamily; easingType = this.options.animationEasingType; // @ts-ignore: Unreachable code error tweenEasingFunction = TWEEN.Easing[easingFamily][easingType]; } // change the tween easing function on the active animation tween if (this._animationTween) { this._animationTween.easing(tweenEasingFunction); } this._animationPropertiesDynamic.easingInfo = { easingFamily: easingFamily, easingType: easingType, tweenEasingFunction: tweenEasingFunction }; }, getAnimationEasingOptions: function (prettyPrint) { var tweenEasingConsoleOptions = {}; var tweenEasingOptions = {}; // @ts-ignore: Unreachable code error Object.keys(TWEEN.Easing).forEach(function (family) { tweenEasingConsoleOptions[family] = { // @ts-ignore: Unreachable code error types: Object.keys(TWEEN.Easing[family]).join('", "') }; tweenEasingOptions[family] = { // @ts-ignore: Unreachable code error types: Object.keys(TWEEN.Easing[family]) }; }); if (!!prettyPrint) { console.table(tweenEasingConsoleOptions); } return tweenEasingOptions; }, playAnimation: function () { this.options.animationStarted = true; this._redrawCanvas(); }, stopAnimation: function () { this.options.animationStarted = false; this._redrawCanvas(); }, isFeatureLinked(activeFeatureUniqueId, targetfeature) { if (activeFeatureUniqueId === null) { return true; } var linkedDesternations = this.featuresMap[activeFeatureUniqueId]; var result = true; if (linkedDesternations) { result = linkedDesternations.properties.linkedTo.some(function (item) { return item.targetId == targetfeature.properties.origin_id; }) } return result; }, clearAllPathSelections: function () { this.originAndDestinationGeoJsonPoints.features.forEach(function (feature) { feature.properties._isSelectedForPathDisplay = false; }); this._resetCanvas(); }, _insertCustomCanvasElement: function (map, options) { var canvas = L.DomUtil.create('canvas', 'leaflet-zoom-animated'); var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']); canvas.style[originProp] = '50% 50%'; var pane = map.getPane(options.pane); pane.insertBefore(canvas, pane.firstChild); //@ts-ignore // oms = new OMS_resource.OverlappingMarkerSpiderfier(map, { keepSpiderfied: true }); oms.addListener('spiderfy', function (markers) { markers.forEach(marker => { //Undo selection if (this.activePie == marker.feature.properties.origin_id) { this.activePie = null; } if (this.secondaryActivePie == marker.feature.properties.origin_id) { this.secondaryActivePie = -1; } // Move Pies and re-draw canvas var modifiedFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == marker.feature.properties.origin_id); modifiedFeature.geometry.coordinates = [marker._latlng.lng, marker._latlng.lat]; // Handle Active Pie Children this.pointsMap[marker.feature.properties.origin_id] = [marker._latlng.lng, marker._latlng.lat]; }); this._redrawCanvas(); }.bind(this)); oms.addListener('unspiderfy', function (markers) { markers.forEach(marker => { // Move Pies and re-draw canvas var modifiedFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == marker.feature.properties.origin_id); modifiedFeature.geometry.coordinates = [marker._latlng.lng, marker._latlng.lat]; // Handle Active Pie Children this.pointsMap[marker.feature.properties.origin_id] = [marker._latlng.lng, marker._latlng.lat]; }); this._redrawCanvas(); }.bind(this)); //bind oms for use on Leaflet point tn layer function. L.bind(this); return canvas; }, _tooltipUpdate: function (feature) { var nameAddition = ""; var items = []; var title; if (this.options.sameSlices) { nameAddition = " MOS"; } if (this.secondaryActivePie) { var secondaryFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == this.secondaryActivePie); var secondaryLinkedDetails = feature.properties.linkedTo.find(linkedFeature => linkedFeature.targetId == this.secondaryActivePie); title = feature.properties.facilityName + " - " + secondaryFeature.properties.facilityName; if (this.options.pieChartsDisplayTypes['outbound']) { items.push({ name: "outgoing" + nameAddition, value: secondaryLinkedDetails.inbound, color: this._pieSliceColor("outbound", feature.properties.pieData.outbound.val) }); } if (this.options.pieChartsDisplayTypes['inbound']) { items.push({ name: "incoming" + nameAddition, value: secondaryLinkedDetails.outbound, color: this._pieSliceColor("inbound", feature.properties.pieData.inbound.val) }); } if (this.options.pieChartsDisplayTypes['internal']) { items.push({ name: "internal" + nameAddition, value: feature.properties.pieData.internal.val, color: this._pieSliceColor("internal", feature.properties.pieData.internal.val) }); } if(!this.options.sameSlices){ items.push({ name: "Max Call Starts", value: secondaryLinkedDetails.maxCallStarts ? secondaryLinkedDetails.maxCallStarts.value : "-", color: '#002d5b', secondaryValue: !secondaryLinkedDetails.maxCallStarts ? null : Format.displayDateRanges(secondaryLinkedDetails.maxCallStarts.keys) }); items.push({ name: "Max Call Volume", value: secondaryLinkedDetails.maxCallVolume ? Format.secondsToHMS(secondaryLinkedDetails.maxCallVolume.value) : "-", color: '#002d5b' }); items.push({ name: "", value: secondaryLinkedDetails.maxCallVolume ? Format.secondsToErlangs(secondaryLinkedDetails.maxCallVolume.value) + ' Erlangs' : "", color: '#002d5b', secondaryValue: !secondaryLinkedDetails.maxCallVolume ? null : Format.displayDateRanges(secondaryLinkedDetails.maxCallVolume.keys) }) items.push({ name: "Max Concurrent Calls", value: secondaryLinkedDetails.maxConcurrentCalls ? secondaryLinkedDetails.maxConcurrentCalls.value : "-", color: '#002d5b', secondaryValue: !secondaryLinkedDetails.maxConcurrentCalls ? null : Format.displayDateRanges(secondaryLinkedDetails.maxConcurrentCalls.keys) }); items.push({ name: "Max Bandwidth", value: secondaryLinkedDetails.maxBandwidth ? Format.displayBandwidth(secondaryLinkedDetails.maxBandwidth.value) : "-", color: '#002d5b', secondaryValue: !secondaryLinkedDetails.maxBandwidth ? null : Format.displayDateRanges(secondaryLinkedDetails.maxBandwidth.keys) }); } } else { title = feature.properties.facilityName; if (this.options.pieChartsDisplayTypes['outbound']) { items.push({ name: "outgoing" + nameAddition, value: feature.properties.pieData.outbound.val, color: this._pieSliceColor("outbound", feature.properties.pieData.outbound.val) }); } if (this.options.pieChartsDisplayTypes['inbound']) { items.push({ name: "incoming" + nameAddition, value: feature.properties.pieData.inbound.val, color: this._pieSliceColor("inbound", feature.properties.pieData.inbound.val) }); } if (this.options.pieChartsDisplayTypes['internal']) { items.push({ name: "internal" + nameAddition, value: feature.properties.pieData.internal.val, color: this._pieSliceColor("internal", feature.properties.pieData.internal.val) }); } if(!this.options.sameSlices){ items.push({ name: "Max Call Starts", value: feature.properties.maxCallStarts ? feature.properties.maxCallStarts.value : "-", color: '#002d5b', secondaryValue: !feature.properties.maxCallStarts ? null : Format.displayDateRanges(feature.properties.maxCallStarts.keys) }); items.push({ name: "Max Call Volume", value: feature.properties.maxCallVolume ? Format.secondsToHMS(feature.properties.maxCallVolume.value) : "-", color: '#002d5b' }); items.push({ name: "", value: feature.properties.maxCallVolume ? Format.secondsToErlangs(feature.properties.maxCallVolume.value) + ' Erlangs' : "-" , color: '#002d5b', secondaryValue: !feature.properties.maxCallVolume ? null : Format.displayDateRanges(feature.properties.maxCallVolume.keys) }) items.push({ name: "Max Concurrent Calls", value: feature.properties.maxConcurrentCalls ? feature.properties.maxConcurrentCalls.value : "-", color: '#002d5b', secondaryValue: !feature.properties.maxConcurrentCalls ? null : Format.displayDateRanges(feature.properties.maxConcurrentCalls.keys) }); items.push({ name: "Max Bandwidth", value: feature.properties.maxBandwidth ? Format.displayBandwidth(feature.properties.maxBandwidth.value) : "-", color: '#002d5b', secondaryValue: !feature.properties.maxBandwidth ? null : Format.displayDateRanges(feature.properties.maxBandwidth.keys) }); } } var dataPoint = { title: title, items: items }; }, _modifyInteractionEvent: function (e) { if (e.type == "click") { // nullify secondary pie if it is clicked on if (this.secondaryActivePie == e.layer.feature.properties.origin_id) { this.secondaryActivePie = null; var activeFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == this.activePie); this._tooltipUpdate(activeFeature); this._redrawCanvas(); } // This is set to disable the selection when expanding clusters else if (this.secondaryActivePie < 0) { this.secondaryActivePie = null; } // not clicking on the main pie set clicked pie as secondary active else if (this.activePie != e.layer.feature.properties.origin_id && this.activePie) { // A secondary Pie can only be set if it is linked to the primary pie var activeFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == this.activePie); if (activeFeature.properties.linkedTo.find(feature => feature.targetId == e.layer.feature.properties.origin_id)) { this.secondaryActivePie = e.layer.feature.properties.origin_id; this._tooltipUpdate(activeFeature); this._redrawCanvas(); } } // otherwise the active pie was clicked and we will toggle the keep selection option. else if (this.activePie) { this.keepCurrentSelection = !this.keepCurrentSelection; // the secondary pie should not persist if the primary pie is selected or deselected this.secondaryActivePie = null; } } else if (e.type == "mouseover" && this.keepCurrentSelection === false) { this.activePie = e.layer.feature.properties.origin_id; this._tooltipUpdate(e.layer.feature); this._redrawCanvas(); } else if (e.type == "mouseout" && this.keepCurrentSelection === false) { this.activePie = null; setTimeout(() => { console.log("received.data.map_data.hide"); }, 100); this._redrawCanvas(); } }, _animateZoom: function (e) { // see: https://github.com/Leaflet/Leaflet.heat if (!this.activePie) { oms.unspiderfy(); } //oms.unspiderfy(); var scale = this._map.getZoomScale(e.zoom); var offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos()); if (L.DomUtil.setTransform) { this._customCanvases.forEach(function (canvas) { L.DomUtil.setTransform(canvas, offset, scale); }); } else { this._customCanvases.forEach(function (canvas) { canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')'; }); } }, _resizeCanvas: function () { // update the canvas size var size = this._map.getSize(); this._customCanvases.forEach(function (canvas) { canvas.width = size.x; canvas.height = size.y; }); this._resetCanvas(); }, _drawPieSlice: function (ctx, point, radius, startAngle, sliceAngle, color, text, type) { var endAngle = startAngle + sliceAngle; // Draw shadow on the background circle if (type == '') { ctx.shadowColor = 'black'; ctx.shadowBlur = 5; } ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(point.x, point.y); ctx.arc(point.x, point.y, radius, startAngle, endAngle); ctx.closePath(); ctx.fill(); // reset shadow to 0 incase it was set ctx.shadowBlur = 0; ctx.font = this.options.pieChartSliceFontSize + "px " + this.options.pieChartSliceFont; ctx.fillStyle = "#ffffff"; var a = ((endAngle - startAngle) / 2) + startAngle; var textrX = 0; var textrY = 0; ctx.fillStyle = '#FFFFFF'; ctx.font = "10px Arial"; if ((2 * Math.PI) == (endAngle - startAngle)) { textrX = point.x; textrY = point.y; //ctx.fillStyle = '#FFFFFF'; //ctx.font = "10px Arial"; //ctx.fillText(type.toUpperCase(), textrX - (ctx.measureText(type.toUpperCase()).width / 2), textrY); this.pieSliceLabels.push({ 'x': textrX - (ctx.measureText(type.toUpperCase()).width / 2), 'y': textrY, 'text': type.toUpperCase(), 'fillStyle': '#FFFFFF', 'font': "10px Arial" }); textrY = point.y + 5; } else { textrX = point.x + (radius * 0.55) * Math.cos(a); textrY = point.y + (radius * 0.55) * Math.sin(a); } // ctx.fillText(text, textrX - (ctx.measureText(text).width / 2), textrY + (this.options.pieChartSliceFontSize / 2)); ctx.font = this.options.pieChartSliceFontSize + "px " + this.options.pieChartSliceFont this.pieSliceLabels.push({ 'x': textrX - (ctx.measureText(text).width / 2), 'y': textrY + (this.options.pieChartSliceFontSize / 2), 'text': text, 'fillStyle': '#FFFFFF', 'font': this.options.pieChartSliceFontSize + "px " + this.options.pieChartSliceFont }); // Add INC, OUT to if (this.options.sameSlices && type != 'internal') { textrX = point.x + (radius * 0.55) * Math.cos(a); textrY = point.y - 10 + (radius * 0.55) * Math.sin(a); var label = ''; if (type == 'inbound') { label = 'INC'; } else if (type == 'outbound') { label = 'OUT'; } ctx.font = this.options.pieChartSliceTinyFontSize + "px " + this.options.pieChartSliceFont; this.pieSliceLabels.push({ 'x': textrX - (ctx.measureText(text).width / 2), 'y': textrY + (this.options.pieChartSliceTinyFontSize / 2), 'text': label, 'fillStyle': '#FFFFFF', 'font': this.options.pieChartSliceTinyFontSize + "px " + this.options.pieChartSliceFont }); } return endAngle; }, _drawPieSlicesLabels: function (ctx) { this.pieSliceLabels.forEach(function (item) { ctx.fillStyle = item.fillStyle; ctx.font = item.font; ctx.fillText(item.text, item.x, item.y); }); this.pieSliceLabels = []; }, _getPieChartRadius: function (items) { var minsize = 30; var maxsize = 30; var val = maxsize; if (items.internal && items.inbound && items.outbound) { val = items.internal.val + items.inbound.val + items.outbound.val; } if (val < minsize) { val = minsize; } if (val > maxsize) { val = maxsize; } return val; }, _sliceAngle: function (sliceStep, sliceVal, totalValue, enabledCount = 0) { var angle = (2 * Math.PI * ((this.options.sameSlices ? sliceStep : sliceVal) / totalValue)); if ((sliceVal == totalValue) && totalValue == 0 && this.options.sameSlices) { if (enabledCount == 1) { return 2 * Math.PI; } else if (enabledCount == 2) { return Math.PI; } } return angle; }, _sliceValueLabel: function (sliceVal, enablesSlices) { if (this.options.sameSlices) { return sliceVal; } return (sliceVal == 0 && enablesSlices > 1) ? "" : sliceVal; }, _pieSliceColor: function (type, value) { if (this.options.sameSlices) { if (value == 0) { return '#002d5b'; } return this.options.callQualityColors.find(colorOption => { return colorOption.max >= value; }).color; } else { return this.options.pieChartsColors[type]; } }, _lineColor: function (color, value) { if (this.options.sameSlices) { if (value == 0) { return '#002d5b'; } return this.options.callQualityColors.find(colorOption => { return colorOption.max >= value; }).color; } else { return color; } }, _drawPieChart: function (options) { var pieChartsDisplayTypes = Object.assign({}, this.options.pieChartsDisplayTypes); var totalValue = 0; options.ctx.fillStyle = '#FF9733'; options.ctx.beginPath(); options.ctx.moveTo(options.point.x, options.point.y); var startAngle = -90 * Math.PI / 180; //options.ctx.globalAlpha = 1; var displayType = 'internal'; // draw blanc grey circle this._drawPieSlice( options.ctx, options.point, options.volume, 0, 2 * Math.PI, '#002d5b', '', '' ); if (options.selected) { totalValue = 0; if (this.options.pieChartsDisplayTypes['internal']) { this._drawPieSlice( options.ctx, options.point, options.volume, 0, 2 * Math.PI, this._pieSliceColor("internal", options.pieData.internal.val), options.pieData.internal.val, "internal" ); } Object.keys(pieChartsDisplayTypes).forEach(function (key) { pieChartsDisplayTypes[key] = (key == displayType); }) var textrX = options.point.x; var textrY = options.point.y - 60; options.ctx.fillStyle = '#08315A'; options.ctx.font = "20px Arial"; options.ctx.fillText(options.facilityName, textrX - (options.ctx.measureText(options.facilityName).width / 2), textrY); //var textrY = options.point.y - 60; //options.ctx.fillStyle = '#7E94AB'; //options.ctx.font = "16px Arial"; //options.ctx.fillText(options.usState, textrX - (options.ctx.measureText(options.usState).width / 2), textrY); } else if (options.active) { totalValue = 0; var enablesSlices = 0; let outboundValue = 0; let inboundValue = 0; if (this.options.pieChartsDisplayTypes['inbound']) { totalValue += options.pieData.inbound.val enablesSlices++; } if (this.options.pieChartsDisplayTypes['outbound']) { totalValue += options.pieData.outbound.val enablesSlices++; } if (options.pieData.inbound.val == options.pieData.outbound.val && options.pieData.outbound.val == 0) { outboundValue = 0; inboundValue = 0; if (this.options.pieChartsDisplayTypes['inbound'] && this.options.pieChartsDisplayTypes['outbound']) { totalValue = 2; options.pieData.inbound.val = 1; options.pieData.outbound.val = 1; } else if (this.options.pieChartsDisplayTypes['inbound']) { totalValue = 1; options.pieData.inbound.val = 1; options.pieData.outbound.val = 0; } else if (this.options.pieChartsDisplayTypes['outbound']) { totalValue = 1; options.pieData.inbound.val = 0; options.pieData.outbound.val = 1; } } else { inboundValue = this._sliceValueLabel(options.pieData.inbound.val, enablesSlices); outboundValue = this._sliceValueLabel(options.pieData.outbound.val, enablesSlices); } sliceStep = totalValue / enablesSlices; if (this.options.pieChartsDisplayTypes['inbound']) { startAngle = this._drawPieSlice( options.ctx, options.point, options.volume, startAngle, this._sliceAngle(sliceStep, options.pieData.inbound.val, totalValue), this._pieSliceColor("inbound", options.pieData.inbound.val), inboundValue, "inbound" ); } if (this.options.pieChartsDisplayTypes['outbound']) { startAngle = this._drawPieSlice( options.ctx, options.point, options.volume, startAngle, this._sliceAngle(sliceStep, options.pieData.outbound.val, totalValue), this._pieSliceColor("outbound", options.pieData.outbound.val), outboundValue, "outbound" ); } } else { totalValue = 0; var enablesSlices = 0; if (this.options.pieChartsDisplayTypes['internal']) { totalValue += options.pieData.internal.val enablesSlices++; } if (this.options.pieChartsDisplayTypes['inbound']) { totalValue += options.pieData.inbound.val enablesSlices++; } if (this.options.pieChartsDisplayTypes['outbound']) { totalValue += options.pieData.outbound.val enablesSlices++; } var sliceStep = totalValue / enablesSlices; if (this.options.pieChartsDisplayTypes['internal']) { startAngle = this._drawPieSlice( options.ctx, options.point, options.volume, startAngle, this._sliceAngle(sliceStep, options.pieData.internal.val, totalValue, enablesSlices), this._pieSliceColor("internal", options.pieData.internal.val), this._sliceValueLabel(options.pieData.internal.val, enablesSlices), "internal" ); } if (this.options.pieChartsDisplayTypes['inbound']) { startAngle = this._drawPieSlice( options.ctx, options.point, options.volume, startAngle, this._sliceAngle(sliceStep, options.pieData.inbound.val, totalValue, enablesSlices), this._pieSliceColor("inbound", options.pieData.inbound.val), this._sliceValueLabel(options.pieData.inbound.val, enablesSlices), "inbound" ); } if (this.options.pieChartsDisplayTypes['outbound']) { this._drawPieSlice( options.ctx, options.point, options.volume, startAngle, this._sliceAngle(sliceStep, options.pieData.outbound.val, totalValue, enablesSlices), this._pieSliceColor("outbound", options.pieData.outbound.val), this._sliceValueLabel(options.pieData.outbound.val, enablesSlices), "outbound" ); } } //draw all labels at the end of all other elements this._drawPieSlicesLabels(options.ctx); }, _resetCanvas: function () { // update the canvas position and redraw its content var topLeft = this._map.containerPointToLayerPoint([0, 0]); this._customCanvases.forEach(function (canvas) { L.DomUtil.setPosition(canvas, topLeft); }); this._redrawCanvas(); } , _resetCanvasAndWrapGeoJsonCircleMarkers: function () { this._resetCanvas(); // Leaflet will redraw a CircleMarker when its latLng is changed // sometimes they are drawn 2+ times if this occurs during many "move" events // so for now, only chang CircleMarker latlng after a single "moveend" event // this._wrapGeoJsonCircleMarkers(); } , _animator: function (time) { this._animationCanvasElement.getContext('2d') .clearRect(0, 0, this._animationCanvasElement.width, this._animationCanvasElement.height); this._drawSelectedCanvasPaths(true, false); // draw it again to give the appearance of a moving dot with a new lineDashOffset // @ts-ignore: Unreachable code error TWEEN.update(time); this._animationFrameId = L.Util.requestAnimFrame(this._animator, this); }, _redrawCanvas: function () { // draw canvas content (only the Bezier curves) if (this._map && this.originAndDestinationGeoJsonPoints) { this._clearCanvas(); // loop over each of the "selected" features and re-draw the canvas paths if (this._animationFrameId) { L.Util.cancelAnimFrame(this._animationFrameId); } if (this.options.animationStarted) { // start animation loop this._animator(); } this._drawSelectedCanvasPaths(false, false); //draw pie charts separatly to prevent overlaps by other canvas elments this._drawSelectedCanvasPaths(false, true); } }, _clearCanvas: function () { this._customCanvases.forEach(function (canvas) { canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); }); if (this._animationFrameId) { L.Util.cancelAnimFrame(this._animationFrameId); } }, mapValues(x, in_min, in_max, out_min, out_max) { in_max = this.options.mapValueMaxRange; var out = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; if (out > out_max) { return out_max; } return out; }, getSinglePoint(origin) { var originLatLng = this._wrapAroundLatLng(L.latLng(origin)); return originLatLng; }, getOriginAndDesternationPoint(origin, desternation, volumeUp, volumeDown) { if (this.options.sameSlices) { volumeUp = 10; volumeDown = 10; } else { volumeUp = this.mapValues(volumeUp, 0, 2000, 4, 25); volumeDown = this.mapValues(volumeDown, 0, 2000, 4, 25); } var screenOriginPoint = this._map.latLngToContainerPoint(this.getSinglePoint(origin)); var screenDestinationPoint = this._map.latLngToContainerPoint(this.getSinglePoint(desternation)); var screenOriginPointUp = Object.assign({}, screenOriginPoint); var screenOriginPointDown = Object.assign({}, screenOriginPoint); var screenDestinationPointUp = Object.assign({}, screenDestinationPoint); var screenDestinationPointDown = Object.assign({}, screenDestinationPoint); if (screenDestinationPoint.x > screenOriginPoint.x) { screenOriginPointUp.x += (volumeUp / 2); screenOriginPointDown.x -= (volumeDown / 2); if (screenDestinationPoint.y > screenOriginPoint.y) { screenDestinationPointUp.y -= (volumeUp / 2); screenDestinationPointDown.y += (volumeDown / 2); } else { screenDestinationPointUp.y += (volumeUp / 2); screenDestinationPointDown.y -= (volumeDown / 2); } } else { if (screenDestinationPoint.y > screenOriginPoint.y) { //-45,-45 screenDestinationPointUp.y += (volumeUp / 2); screenDestinationPointDown.y -= (volumeDown / 2); } else { //-45,0 screenDestinationPointUp.y -= (volumeUp / 2); screenDestinationPointDown.y += (volumeDown / 2); } screenOriginPointUp.x += (volumeUp / 2); screenOriginPointDown.x -= (volumeDown / 2); } return { 'screenOriginPointUp': screenOriginPointUp, 'screenOriginPointDown': screenOriginPointDown, 'screenDestinationPointUp': screenDestinationPointUp, 'screenDestinationPointDown': screenDestinationPointDown, 'screenOriginPoint': screenOriginPoint, 'screenDestinationPoint': screenDestinationPoint } }, _drawSelectedCanvasPaths: function (animate, onlyPieCharts) { if (animate == false && onlyPieCharts == true) { } var ctx = animate ? this._animationCanvasElement.getContext('2d') : this._canvasElement.getContext('2d'); var originAndDestinationFieldIds = this.options.originAndDestinationFieldIds; this.originAndDestinationGeoJsonPoints.features.forEach(function (feature) { var originXCoordinate = feature.geometry.coordinates[0]; var originYCoordinate = feature.geometry.coordinates[1]; var originAndDesternationPointsd = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [0, 0], 0, 10); var hovered = this.activePie == feature.properties.origin_id; if (onlyPieCharts == true && animate == false) { var screenOriginPoint = this._map.latLngToContainerPoint(this.getSinglePoint([originYCoordinate, originXCoordinate])); // if there is an active pie if (this.activePie) { // if the current feature iteration is the active pie if (this.activePie == feature.properties.origin_id) { // Draw opaque pies first ctx.globalAlpha = 0.4; this.originAndDestinationGeoJsonPoints.features.forEach(feature => { var originXCoordinate = feature.geometry.coordinates[0]; var originYCoordinate = feature.geometry.coordinates[1]; var originAndDesternationPointsd = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [0, 0], 0, 10); var pieChartOptions = { facilityName: feature.properties.facilityName, selected: this.activePie == feature.properties.origin_id, usState: feature.properties.usState, active: false, pieData: feature.properties.pieData, ctx: ctx, point: originAndDesternationPointsd.screenOriginPoint, volume: feature.properties._volume }; this._drawPieChart(pieChartOptions); }); ctx.globalAlpha = 1.0; // only draw primary and secondary pies if secondaryActivePie is defined if (this.secondaryActivePie) { var secondaryFeature = this.originAndDestinationGeoJsonPoints.features.find(feature => feature.properties.origin_id == this.secondaryActivePie); var secondaryLinkedDetails = feature.properties.linkedTo.find(linkedFeature => linkedFeature.targetId == this.secondaryActivePie); var originXCoordinate = secondaryFeature.geometry.coordinates[0]; var originYCoordinate = secondaryFeature.geometry.coordinates[1]; var originAndDesternationPointsd = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [0, 0], 0, 10); let pieData = { inbound: { 'title': 'test 1', 'val': secondaryLinkedDetails.outbound, 'type': 'inbound' }, outbound: { 'title': 'test 1', 'val': secondaryLinkedDetails.inbound, 'type': 'outbound' }, internal: { 'title': 'test 1', 'val': secondaryLinkedDetails.outbound, 'type': 'outbound' } }; let pieChartOptions = { facilityName: secondaryFeature.properties.facilityName, selected: this.activePie == secondaryFeature.properties.origin_id, usState: secondaryFeature.properties.usState, active: true, pieData: pieData, ctx: ctx, point: originAndDesternationPointsd.screenOriginPoint, volume: this._getPieChartRadius(pieData) }; this._drawPieChart(pieChartOptions); } // otherwise draw all pies linked ot activePie else { // Draw Linked to First so active pie is drawn on top. feature.properties.linkedTo.forEach(function (des) { var symbol = this._getSymbolProperties(feature, this.options.canvasBezierStyle); var destinationFeature = this.featuresMap[des.targetId]; let pieData = { inbound: { 'title': 'test 1', 'val': des.outbound, 'type': 'inbound' }, outbound: { 'title': 'test 1', 'val': des.inbound, 'type': 'outbound' }, internal: { 'title': 'test 1', 'val': des.outbound, 'type': 'outbound' } }; if(!this.pointsMap[des.targetId]) { return; } var destinationXCoordinate = this.pointsMap[des.targetId][0]; var destinationYCoordinate = this.pointsMap[des.targetId][1]; var originAndDesternationPoints = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [destinationYCoordinate, destinationXCoordinate], des.outbound, des.inbound); let pieChartOptions = { facilityName: destinationFeature.properties.facilityName, selected: false, usState: destinationFeature.properties.usState, active: true, pieData: pieData, ctx: ctx, point: originAndDesternationPoints.screenDestinationPoint, volume: this._getPieChartRadius(pieData) }; this._drawPieChart(pieChartOptions); }.bind(this)); } // Now draw active pie on top var pieChartOptions = { facilityName: feature.properties.facilityName, selected: this.activePie == feature.properties.origin_id, usState: feature.properties.usState, active: true, pieData: feature.properties.pieData, ctx: ctx, point: screenOriginPoint, volume: feature.properties._volume }; this._drawPieChart(pieChartOptions); } } else { var pieChartOptions = { facilityName: feature.properties.facilityName, selected: this.activePie == feature.properties.origin_id, usState: feature.properties.usState, active: false, pieData: feature.properties.pieData, ctx: ctx, point: originAndDesternationPointsd.screenOriginPoint, volume: feature.properties._volume }; this._drawPieChart(pieChartOptions); } } else { // This is where LINES are drawn if (feature.properties._isSelectedForPathDisplay) { if (animate) { if (hovered && feature.properties.linkedTo && feature.properties.linkedTo.length) { var symbol = this._getSymbolProperties(feature, this.options.canvasBezierStyle); // Only draw lines between activePie and secondaryActivePie if secondaryActivePie is defined if (this.secondaryActivePie) { var des = feature.properties.linkedTo.find(linkedPie => linkedPie.targetId == this.secondaryActivePie); var symbolUp; var symbolDown; var destinationXCoordinate = this.pointsMap[des.targetId][0]; var destinationYCoordinate = this.pointsMap[des.targetId][1]; var originXCoordinate = feature.geometry.coordinates[0]; var originYCoordinate = feature.geometry.coordinates[1]; var originAndDesternationPoints = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [destinationYCoordinate, destinationXCoordinate], des.outbound, des.inbound); symbol = this._getSymbolProperties(feature, this.options.animatedCanvasBezierStyle); symbolUp = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.inbound, value: des.inbound }, this.options.animatedCanvasBezierStyleUp || {}); symbolDown = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.outbound, value: des.outbound }, this.options.animatedCanvasBezierStyleDown || {}); if (this.options.pieChartsDisplayTypes['outbound']) { ctx.beginPath(); this._animateCanvasLineSymbol(ctx, symbolDown, originAndDesternationPoints.screenOriginPointDown, originAndDesternationPoints.screenDestinationPointDown, true); ctx.stroke(); ctx.closePath(); } if (this.options.pieChartsDisplayTypes['inbound']) { ctx.beginPath(); this._animateCanvasLineSymbol(ctx, symbolUp, originAndDesternationPoints.screenOriginPointUp, originAndDesternationPoints.screenDestinationPointUp, false); ctx.stroke(); ctx.closePath(); } } else { feature.properties.linkedTo.forEach(function (des) { var symbolUp; var symbolDown; if(!this.pointsMap[des.targetId]) { return; } var destinationXCoordinate = this.pointsMap[des.targetId][0]; var destinationYCoordinate = this.pointsMap[des.targetId][1]; var originXCoordinate = feature.geometry.coordinates[0]; var originYCoordinate = feature.geometry.coordinates[1]; var originAndDesternationPoints = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [destinationYCoordinate, destinationXCoordinate], des.outbound, des.inbound); symbol = this._getSymbolProperties(feature, this.options.animatedCanvasBezierStyle); symbolUp = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.inbound, value: des.inbound }, this.options.animatedCanvasBezierStyleUp || {}); symbolDown = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.outbound, value: des.outbound }, this.options.animatedCanvasBezierStyleDown || {}); if (this.options.pieChartsDisplayTypes['outbound']) { ctx.beginPath(); this._animateCanvasLineSymbol(ctx, symbolDown, originAndDesternationPoints.screenOriginPointDown, originAndDesternationPoints.screenDestinationPointDown, true); ctx.stroke(); ctx.closePath(); } if (this.options.pieChartsDisplayTypes['inbound']) { ctx.beginPath(); this._animateCanvasLineSymbol(ctx, symbolUp, originAndDesternationPoints.screenOriginPointUp, originAndDesternationPoints.screenDestinationPointUp, false); ctx.stroke(); ctx.closePath(); } }.bind(this)); } } } else { if (hovered && feature.properties.linkedTo && feature.properties.linkedTo.length) { var symbol = this._getSymbolProperties(feature, this.options.canvasBezierStyle); // Only draw path between primary and secondary pies is secondary is defined if (this.secondaryActivePie) { var des = feature.properties.linkedTo.find(linkedPie => linkedPie.targetId == this.secondaryActivePie); var symbolUp; var symbolDown; var destinationXCoordinate = this.pointsMap[des.targetId][0]; var destinationYCoordinate = this.pointsMap[des.targetId][1]; var originAndDesternationPoints = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [destinationYCoordinate, destinationXCoordinate], des.outbound, des.inbound); symbolUp = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.inbound, value: des.inbound }, this.options.canvasBezierStyleUp || {}); symbolDown = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.outbound, value: des.outbound }, this.options.canvasBezierStyleDown || {}); ctx.globalAlpha = 0.7; if (this.options.pieChartsDisplayTypes['inbound']) { ctx.beginPath(); this._applyAnimatedCanvasLineSymbol(ctx, symbolUp, originAndDesternationPoints.screenOriginPointUp, originAndDesternationPoints.screenDestinationPointUp); ctx.stroke(); ctx.closePath(); } if (this.options.pieChartsDisplayTypes['outbound']) { ctx.beginPath(); this._applyAnimatedCanvasLineSymbol(ctx, symbolDown, originAndDesternationPoints.screenOriginPointDown, originAndDesternationPoints.screenDestinationPointDown); ctx.stroke(); ctx.closePath(); } } // Otherwise draw paths to all pies linked to activePie else { feature.properties.linkedTo.forEach(function (des) { var symbolUp; var symbolDown; if(!this.pointsMap[des.targetId]) { return; } var destinationXCoordinate = this.pointsMap[des.targetId][0]; var destinationYCoordinate = this.pointsMap[des.targetId][1]; var originAndDesternationPoints = this.getOriginAndDesternationPoint([originYCoordinate, originXCoordinate], [destinationYCoordinate, destinationXCoordinate], des.outbound, des.inbound); symbolUp = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.inbound, value: des.inbound }, this.options.canvasBezierStyleUp || {}); symbolDown = Object.assign({}, symbol, { lineWidth: this.options.sameSlices ? 10 : des.outbound, value: des.outbound }, this.options.canvasBezierStyleDown || {}); ctx.globalAlpha = 0.7; if (this.options.pieChartsDisplayTypes['inbound']) { ctx.beginPath(); this._applyAnimatedCanvasLineSymbol(ctx, symbolUp, originAndDesternationPoints.screenOriginPointUp, originAndDesternationPoints.screenDestinationPointUp); ctx.stroke(); ctx.closePath(); } if (this.options.pieChartsDisplayTypes['outbound']) { ctx.beginPath(); this._applyAnimatedCanvasLineSymbol(ctx, symbolDown, originAndDesternationPoints.screenOriginPointDown, originAndDesternationPoints.screenDestinationPointDown); ctx.stroke(); ctx.closePath(); } }.bind(this)); } } // this._drawPieChart(ctx, originAndDesternationPointsd.screenOriginPoint, feature); } } } } , this ); }, _getSymbolProperties: function (feature, canvasSymbolConfig) { // get the canvas symbol properties var symbol; var filteredSymbols; if (canvasSymbolConfig.type === 'simple') { symbol = canvasSymbolConfig.symbol; } else if (canvasSymbolConfig.type === 'uniqueValue') { filteredSymbols = canvasSymbolConfig.uniqueValueInfos.filter(function (info) { return info.value === feature.properties[canvasSymbolConfig.field]; }); symbol = filteredSymbols[0].symbol; } else if (canvasSymbolConfig.type === 'classBreaks') { filteredSymbols = canvasSymbolConfig.classBreakInfos.filter(function (info) { return ( info.classMinValue <= feature.properties[canvasSymbolConfig.field] && info.classMaxValue >= feature.properties[canvasSymbolConfig.field] ); }); if (filteredSymbols.length) { symbol = filteredSymbols[0].symbol; } else { symbol = canvasSymbolConfig.defaultSymbol; } } return symbol; }, _applyAnimatedCanvasLineSymbol: function (ctx, symbolObject, screenOriginPoint, screenDestinationPoint) { ctx.lineCap = symbolObject.lineCap; ctx.lineWidth = this.options.sameSlices ? 10 : this.mapValues(symbolObject.lineWidth, 0, 2000, 4, 25); ctx.strokeStyle = this._lineColor(symbolObject.strokeStyle, symbolObject.value); ctx.shadowBlur = symbolObject.shadowBlur; ctx.shadowColor = symbolObject.shadowColor; ctx.moveTo(screenOriginPoint.x, screenOriginPoint.y); ctx.bezierCurveTo(screenOriginPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y); }, _animateCanvasLineSymbol: function (ctx, symbolObject, screenOriginPoint, screenDestinationPoint, direction) { ctx.lineCap = symbolObject.lineCap; ctx.lineWidth = this.options.sameSlices ? 10 : this.mapValues(symbolObject.lineWidth, 0, 2000, 4, 25); ctx.strokeStyle = this._lineColor(symbolObject.strokeStyle, symbolObject.value); ctx.shadowBlur = symbolObject.shadowBlur; ctx.shadowColor = symbolObject.shadowColor; //ctx.setLineDash([symbolObject.lineDashOffsetSize, (this._animationPropertiesStatic.resetOffset - symbolObject.lineDashOffsetSize)]); ctx.setLineDash([10, 10]); if (direction) { ctx.lineDashOffset = -this._animationPropertiesStatic.offset; // this makes the dot appear to move when the entire top canvas is redrawn } else { ctx.lineDashOffset = +this._animationPropertiesStatic.offset; // this makes the dot appear to move when the entire top canvas is redrawn } ctx.moveTo(screenOriginPoint.x, screenOriginPoint.y); ctx.bezierCurveTo(screenOriginPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y); } , _wrapGeoJsonCircleMarkers: function () { // ensure that the GeoJson point features, // which are drawn on the map as individual CircleMarker layers, // will be drawn beyond +/-180 longitude this.eachLayer(function (layer) { var wrappedLatLng = this._wrapAroundLatLng(layer.getLatLng()); layer.setLatLng(wrappedLatLng); }, this); } , _wrapAroundLatLng: function (latLng) { if (this._map && this.options.wrapAroundCanvas) { var wrappedLatLng = latLng.clone(); var mapCenterLng = this._map.getCenter().lng; var wrapAroundDiff = mapCenterLng - wrappedLatLng.lng; if (wrapAroundDiff < -180 || wrapAroundDiff > 180) { wrappedLatLng.lng += (Math.round(wrapAroundDiff / 360) * 360); } return wrappedLatLng; } else { return latLng; } } }); return CanvasFlowmapLayer; }, L)); }