JsPlumb Dynamic Endpoints as Connection Overlays - jsplumb

I am trying to create dynamic endpoints as overlays on my connections and running into issues. I am trying to model what this person has here on SO:
jsPlumb connecting custom overlays - endpoint not moved
However, no matter what I try to do when I get to this point:
var overlay_div = $(connection.overlays[0].canvas);
I cannot get the connection to be recognized. I've tried to put this logic in the bind connection but that didn't work either when trying to establish the connection overlay. Any assistance on this would be extremely helpful.

http://jsfiddle.net/nitincool4urchat/c3b514wf/14/
First, Create custom elements as overlays
Second, Make sure that these elements have unique IDs
Third, Bind to the connection event to create endPoints on these custom-overlays.
jsPlumb.ready(function() {
// setup some defaults for jsPlumb.
instance = jsPlumb.getInstance({
Endpoint : ["Dot", {radius:2}],
HoverPaintStyle : {strokeStyle:"#1e8151", lineWidth:4 },
ConnectionOverlays : [
[ "Arrow", {
location:1,
id:"arrow",
length:14,
foldback:0.8
}]
,["Custom", {
create: function(component) {
return connectionNode();
},
location:0.5
}]
//,[ "Label", { label:"Connect To", id:"label", cssClass:"aLabel" }]
],
Container: "flowchart-demo"
});
var relationEndpoint = {
endpoint: ["Dot", { radius: 2 }],
isSource: true,
connector: ["Flowchart", { stub: [40, 60], cornerRadius: 5, alwaysRespectStubs: true }],
connectorStyle: { strokeStyle: "#5c96bc", lineWidth: 4, outlineColor: "transparent", outlineWidth: 4 },
maxConnections: 5,
onMaxConnections: function (info, e) {
alert("Maximum connections (" + info.maxConnections + ") reached");
},
isTarget: true,
dropOptions: {
tolerance: "touch",
hoverClass: "dropHover",
activeClass: "dragActive"
},
beforeDetach: function (conn) {
return confirm("Detach connection?");
}
};
function connectionNode() {
//var overlay_div = $(connection.ConnectionOverlays[0].canvas);
//jsPlumb.addEndpoint({ anchor: ["Perimeter", { shape: "Rectangle" }] }, relationEndpoint);
return $("<div>Custom</div>",{id:Date.now()}).css({border:'1px solid black',background:'green'});
}
var windows = jsPlumb.getSelector(".flowchart-demo .window");
instance.draggable(windows);
instance.bind("connection", function(info) {
console.dir(info.connection);
console.dir(info.connection.getOverlays());
console.dir(info.connection.getOverlays()[1].canvas);
var overlay_div = $(info.connection.getOverlays()[1].canvas);
jsPlumb.addEndpoint(overlay_div,{ anchor: ["Perimeter", { shape: "Rectangle" }] }, relationEndpoint);
});
// suspend drawing and initialise.
instance.doWhileSuspended(function() {
var isFilterSupported = instance.isDragFilterSupported();
// make each ".ep" div a source and give it some parameters to work with. here we tell it
// to use a Continuous anchor and the StateMachine connectors, and also we give it the
// connector's paint style. note that in this demo the strokeStyle is dynamically generated,
// which prevents us from just setting a jsPlumb.Defaults.PaintStyle. but that is what i
// would recommend you do. Note also here that we use the 'filter' option to tell jsPlumb
// which parts of the element should actually respond to a drag start.
// here we test the capabilities of the library, to see if we
// can provide a `filter` (our preference, support by vanilla
// jsPlumb and the jQuery version), or if that is not supported,
// a `parent` (YUI and MooTools). I want to make it perfectly
// clear that `filter` is better. Use filter when you can.
if (isFilterSupported) {
instance.makeSource(windows, {
filter:".ep",
anchor:"Continuous",
connector: ["Flowchart", { stub: [40, 60], cornerRadius: 5, alwaysRespectStubs: true }],
connectorStyle:{ strokeStyle:"#5c96bc", lineWidth:4, outlineColor:"transparent", outlineWidth:4 },
maxConnections:5,
onMaxConnections:function(info, e) {
alert("Maximum connections (" + info.maxConnections + ") reached");
}
});
}
else {
var eps = jsPlumb.getSelector(".ep");
for (var i = 0; i < eps.length; i++) {
var e = eps[i], p = e.parentNode;
instance.makeSource(e, {
parent:p,
anchor:"Continuous",
connector: ["Flowchart", { stub: [40, 60], cornerRadius: 5, alwaysRespectStubs: true }],
connectorStyle:{ strokeStyle:"#5c96bc",lineWidth:4, outlineColor:"transparent", outlineWidth:4 },
maxConnections:5,
onMaxConnections:function(info, e) {
alert("Maximum connections (" + info.maxConnections + ") reached");
}
});
}
}
});
// initialise all '.w' elements as connection targets.
instance.makeTarget(windows, {
dropOptions:{ hoverClass:"dragHover" },
anchor:"Continuous",
allowLoopback:true,
anchor:"Continuous"
});
jsPlumb.fire("jsPlumbDemoLoaded", instance);
});

Related

Phaser 3.17 doesn't setect collision between tilemap layer and player sprite

No collisions between map layer and player sprite. But collisions between world bounds work. What is wrong?
I tried different workarounds that could find online and none of them worked.
Game config
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: "#f",
physics: {
default: 'arcade',
arcade: {
gravity: { y: gameState.gravity },
debug: true
}
},
scene: {
preload,
create,
update
}
};
Code in create() regarding the tilemap and its layers and the character
gameState.map = this.add.tilemap('map');
gameState.dungeonTileset = gameState.map.addTilesetImage('dungeon', 'dungeonTiles');
gameState.backgroundLayer = gameState.map.createStaticLayer('Background', gameState.dungeonTileset);
gameState.mapLayer = gameState.map.createStaticLayer('Map', gameState.dungeonTileset);
gameState.miscLayer = gameState.map.createStaticLayer('Misc', gameState.dungeonTileset);
gameState.mapLayer.setCollisionByExclusion([-1]);
this.physics.world.bounds.width = gameState.mapLayer.width;
this.physics.world.bounds.height = gameState.mapLayer.height;
gameState.player = this.physics.add.sprite(73, 398, 'player', 0);
gameState.player.setCollideWorldBounds(true);
this.physics.add.collider(gameState.player, gameState.mapLayer);
No warning and no errors are coming up in the console. I don't know what to do anymore.
Thanks in advance!
I have addited a little bit the original example so you can see how it looks, I think the problem in your code is the line concerning gameState.mapLayer.setCollisionByExclusion([-1]); but am not sure because I can't see how the tilemap is created
var config = {
type: Phaser.WEBGL,
width: 800,
height: 576,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
loader: {
baseURL: 'https://labs.phaser.io',
crossOrigin: 'anonymous'
},
pixelArt: true,
physics: {
default: 'arcade',
arcade: { gravity: { y: 300 } }
},
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
var map;
var cursors;
var player;
var groundLayer;
function preload ()
{
this.load.image('ground_1x1', 'assets/tilemaps/tiles/ground_1x1.png');
this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/tile-collision-test.json');
this.load.image('player', 'assets/sprites/phaser-dude.png');
}
function create ()
{
map = this.make.tilemap({ key: 'map' });
var groundTiles = map.addTilesetImage('ground_1x1');
map.createDynamicLayer('Background Layer', groundTiles, 0, 0);
groundLayer = map.createDynamicLayer('Ground Layer', groundTiles, 0, 0);
groundLayer.setCollisionBetween(1, 25);//here
player = this.physics.add.sprite(80, 70, 'player')
.setBounce(0.1);
this.physics.add.collider(player, groundLayer);
cursors = this.input.keyboard.createCursorKeys();
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
this.cameras.main.startFollow(player);
}
function update ()
{
player.body.setVelocityX(0);
if (cursors.left.isDown)
{
player.body.setVelocityX(-200);
}
else if (cursors.right.isDown)
{
player.body.setVelocityX(200);
}
if (cursors.up.isDown && player.body.onFloor())
{
player.body.setVelocityY(-300);
}
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.17.0/dist/phaser.min.js"></script>

SVG animation / TweenMax / Reverse animation

Here is the animation which is running with yoyo = true meaning the animation goes one way and then do the "reverse" animation.
I would like to be able to play the animation in mode reverse without having the yoyo mode.
In other words, I would like to have the second part (the reverse one)
https://codepen.io/chrisgannon/pen/greVXG
var xmlns = "http://www.w3.org/2000/svg",
xlinkns = "http://www.w3.org/1999/xlink",
select = function(s) {
return document.querySelector(s);
},
selectAll = function(s) {
return document.querySelectorAll(s);
}
TweenMax.set('svg', {
visibility: 'visible',
transformOrigin:'50% 50%',
scale:1
})
var mainTl = new TimelineMax({repeat:-1, yoyo:true, repeatDelay:2});
var skySunTl = new TimelineMax({paused:true});
skySunTl.fromTo('#lower', 10, {
stopColor:'#020111'
},{
stopColor:'#CD82A0',
ease:Linear.easeNone
})
.fromTo('#upper', 10, {
stopColor:'#1F1F2B'
},{
stopColor:'#4B4A6A',
ease:Linear.easeNone
},'-=10')
.to('#lower', 10, {
stopColor:'#95DFFF',
ease:Linear.easeNone
})
.to('#upper', 10, {
stopColor:'#94DFFF',
ease:Linear.easeNone
},'-=10')
.to('#lower', 10, {
stopColor:'#f9b681',
ease:Linear.easeNone
})
.to('#upper', 10, {
stopColor:'#eb4a78',
ease:Linear.easeNone
},'-=10')
.fromTo('#sun', 10, {
fill:'#B30200'
},
{
fill:'#EC8323',
ease:Linear.easeNone
},'-=30')
.to('#sun', 10, {
fill:'#FFF',
ease:Linear.easeNone
},'-=20')
.to('#sun', 10, {
fill:'#fefdeb',
ease:Linear.easeNone
},'-=10')
.fromTo('#sun', 15, {
attr:{
cy:410,
r:105
}},{
attr:{
cy:0,
r:90
},
ease:Power1.easeInOut
},'-=30')
.to('#sun', 14, {
attr:{
cy:300,
r:105
},
ease:Sine.easeInOut
},'-=13')
.from('#mainCircleMask circle', 30, {
attr:{
r:500
},
ease:Power1.easeInOut
},'-=30')
var waterTl = new TimelineMax({paused:true});
waterTl.fromTo('#waterCircle', 30, {
fill:'#020111'
},{
fill:'#5DB3C6',
ease:Linear.easeNone
})
.fromTo('#waterRipple', 30, {
fill:'#020111'
},{
fill:'#A5DCE4',
ease:Linear.easeNone
},'-=30')
.fromTo('body', 30, {
backgroundColor:'#020111'
},{
backgroundColor:'#FFF',
ease:Linear.easeNone
},'-=30')
var skySunTween = TweenMax.to(skySunTl, 10, {
time:skySunTl.duration(),
ease:Power2.easeInOut
})
var waterTween= TweenMax.to(waterTl, 10, {
time:waterTl.duration(),
ease:Power2.easeInOut
})
mainTl.add(skySunTween, 0);
mainTl.add(waterTween, 0);
mainTl.timeScale(4);
mainTl.play(0)
//ScrubGSAPTimeline(mainTl)
//tl.timeScale(30)
Thank you
This should do the trick, using a mix of reversed:true in your TimelineMax() constructor. And also using progress(0.5) when you chain your timelines/tweens:
https://codepen.io/jonathan/pen/dNxgVK
I changed this:
var mainTl = new TimelineMax({
repeat:1,
yoyo:true,
repeatDelay:2
});
To this using reversed:true
var mainTl = new TimelineMax({
reversed:true
});
reversed is found in the TimelineMax Docs:
reversed ( value:Boolean ) : *
Gets or sets the animation's reversed state which indicates whether or
not the animation should be played backwards.
And i changed this
mainTl.add(skySunTween, 0);
mainTl.add(waterTween, 0);
mainTl.timeScale(4);
mainTl.play();
To this and added progress(0.5), which represents half way through the timeline
mainTl.add(skySunTween, 0);
mainTl.add(waterTween, 0);
mainTl.timeScale(4);
mainTL.progress(0.5); // added GSAP progress() method
mainTl.play();
.progress( value:Number, suppressEvents:Boolean ) : *
[override] Gets or sets the timeline's progress which is a value
between 0 and 1 indicating the position of the virtual playhead
(excluding repeats) where 0 is at the beginning, 0.5 is halfway
complete, and 1 is complete.
Resources:
progress(): https://greensock.com/docs/#/HTML5/GSAP/TimelineMax/progress/
TimelineMax: https://greensock.com/docs/#/HTML5/GSAP/TimelineMax/

How to prevent Slick plugin from removing event listeners

This is my actual slick gallery, I added an 'click' event listener on the "?" button... but when the gallery hits the breakpoint, slick will remove all the event listeners... how can i prevent that?
$('.qmark-btn').click(function(e) {
e.preventDefault();
// var img = $(this);
alert('test');
});
$('.img-gallery').slick({
slidesToShow: 6,
slidesToScroll: 3,
arrows: false,
rows: 3,
infinite: false,
responsive: [
{
breakpoint: 1200,
settings: {
slidesToShow: 5,
slidesToScroll: 3,
}
},
{
breakpoint: 992,
settings: {
slidesToShow: 4,
slidesToScroll: 3,
}
},
{
breakpoint: 768,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
}
},
{
breakpoint: 600,
settings: {
slidesToShow: 2,
slidesToScroll: 1,
}
}
]
});
I experienced the same problem and fixed it with a small tweak to the cleanUpRows function within slick.js.
Slick.prototype.cleanUpRows = function() {
var _ = this, originalSlides;
if(_.options.rows > 1) {
originalSlides = _.$slides.children().children().clone(true);
originalSlides.removeAttr('style');
_.$slider.empty().append(originalSlides);
}
};
Simply add ".clone(true)".
This copies eventlisteners to originalSlides.
This fixed my issues with Slick when a responsive break was hit.
The problem with cloning is that it only keeps jquery related handlers and will not preserve things like vanilla js events and references to the old node that may exist in your code. To keep all of this, we want to avoid cloning the elements AND avoid using the empty() jquery function which removes all jquery handler, so one solution would be to replace this function by emptying the content with a simple use of innerHTML:
Slick.prototype.cleanUpRows = function() {
var _ = this, originalSlides;
if(_.options.rows > 1) {
originalSlides = _.$slides.children().children().clone(true);
originalSlides.removeAttr('style');
_.$slider.get(0).innerHTML = '';
_.$slider.append(originalSlides);
}
};
or use a documentFragment:
Slick.prototype.cleanUpRows = function() {
var _ = this, originalSlides;
if(_.options.rows > 0) {
originalSlides = _.$slides.children().children()
originalSlides.removeAttr('style');
var fragment = document.createDocumentFragment();
_.$slides.children().each(function(i, element) {
while(element.firstChild) {
fragment.appendChild(element.firstChild);
}
element.parentNode.replaceChild(fragment, element);
})
}
};

ZoomRange Highstock works not correct?

I made a Highstock diagramm and got aproblem with zooming on the yAxis.
I have a Button and 2 textfield to get the wanted min/max values for the axis. With min:0, max: 100 it works well. With min:0, max:80 it doesn't (max will still be 100 in the Diagramm).
If I use the mouse for zooming it works well (even a min of: 3.7 and a max of 3.894 is possible). But using the mouse is not an Option, because in the later Diagramm there will be 3 yAxes with individual zoom.
$(function () {
var seriesOptions = [],
seriesCounter = 0,
names = ['MSFT', 'AAPL', 'GOOG'];
/**
* Create the chart when all data is loaded
* #returns {undefined}
*/
function createChart() {
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 4
},
chart:{
zoomType: 'xy'
},
yAxis: [
{
labels: {
format: '{value}',
},
height: '100%',
opposite: false,
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
],
plotOptions: {
series: {
compare: 'percent'
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2
},
series: seriesOptions
},
function(chart){
$('#btn').click(function(){
var min = temp_min.value,
max = temp_max.value;
chart.yAxis[0].setExtremes((min),(max));
});
});
}
$.each(names, function (i, name) {
$.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=' + name.toLowerCase() + '-c.json&callback=?', function (data) {
if(seriesCounter==0){
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
} else {
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
}
// As we're loading the data asynchronously, we don't know what order it will arrive. So
// we keep a counter and create the chart when all the data is loaded.
seriesCounter += 1;
if (seriesCounter === names.length) {
createChart();
}
});
});
});
JSFiddle
Another Question: Is it possible to set up a scrollbar for the yAxis as well?
Thanks for your help, Patrick
This is related with fact that tickInterval is not regular, so is rounded to value (like 100). The solution is using tickPositioner which calculates ticks, based on extremes which you define.
tickPositioner: function (min,max) {
var positions = [],
tick = Math.floor(min),
increment = Math.ceil((max - min) / 5);
for (tick; tick - increment <= max; tick += increment) {
positions.push(tick);
}
return positions;
},
http://jsfiddle.net/6s11kcwd/
The scrollbar is supported only for xAxis.

A marker can only be added to a layer of type "group" error even though type "group" is defined

I am having trouble with an error that seems obvious but I've tried everything. I must be missing something. I'm running a rails app with Angularjs and angular-leaflet-directives. I am trying to add markers to a specific group. No matter what I do, I keep getting this error:
[AngularJS - Leaflet] A marker can only be added to a layer of type "group"
I know that this error comes up when the overlays type isn't group. The issue is that in my case, it is!
EDIT: here is a plunkr where I recreated the bug: http://plnkr.co/edit/DLCN5RYVr0BheYzTuqkQ?p=preview
Here is my code:
assets/javascript/angular/services/service.js
app.factory('Markers', ["$http", "$q", function($http, $q) {
var Markers = []
var events_markers = []
var stories_markers = []
var defaultIcon = L.icon({
iconUrl: 'assets/dot-grey.png',
iconSize: [15, 15],
iconAnchor: [15, 15],
popupAnchor: [-7, -20]
})
// Getting events + stories from rails API
var event_data = $http.get("/api/v1/events.json"),
story_data = $http.get("/api/v1/stories.json")
// Setting event markers and stories markers
$q.all([event_data, story_data]).then(function(results) {
var data_stories = results[1].data.stories
var data_events = results[0].data.event
for (i=0 ; i < data_stories.length; i++){
for (j=0; j < data_stories[i].locations.length; j++){
var lat = data_stories[i].locations[j].latitude
var lng = data_stories[i].locations[j].longitude
var title = data_stories[i].title
var layer = "stories"
Markers.push({layer: layer, lat:lat, lng:lng, message: title, icon: defaultIcon})
}
}
for (e=0 ; e < data_events.length; e++){
if (data_events[e].latitude != null){
var lat = data_events[e].latitude
var lng = data_events[e].longitude
var title = data_events[e].name
var layer = "events"
Markers.push({layer: layer, lat:lat, lng: lng, message: title, icon: defaultIcon})
}
}
return Markers
});
return {
markers: Markers
}
}]);
assets/javascript/angular/controllers/MapCtrl.js
app.controller("MapCtrl", ['$scope', "$timeout", "leafletData", "Markers", function($scope,
$timeout, leafletData, Markers ){
$scope.isVisible = true;
$scope.markers = Markers.markers;
var bounds = {
northEast:{
lat: 37.86862005954327,
lng: -122.12230682373048
},
southWest:{
lat: 37.68436373334184,
lng: -122.55901336669923
}
}
function setMap($scope, markers, bounds) {
angular.extend($scope, {
maxbounds: bounds,
defaults: {
scrollWheelZoom: false,
maxZoom: 14,
minZoom: 10
},
layers: {
baselayers: {
mapbox:{
name: 'Mapbox Litography',
url: 'http://api.tiles.mapbox.com/v4/{mapid}/{z}/{x}/{y}.png?access_token={apikey}',
type: 'xyz',
layerParams: {
apikey: 'pk.eyJ1IjoibGF1cmVuYmVuaWNob3UiLCJhIjoiQ1BlZGczRSJ9.EVMieITn7lHNi6Ato9wFwg',
mapid: 'laurenbenichou.jm96meb6'
}
}
}
},
overlays: {
stories: {
type: 'group', //Note here that the type is indeed 'group'
name: 'stories',
visible: true
},
events: {
type: 'group',
name: 'events',
visible: false
}
},
markers: markers
});
}
setMap($scope, $scope.markers, bounds)
angular.element(document).ready(function () {
function toggle(){$scope.isVisible = !$scope.isVisible;}
$timeout(toggle, 1000);
});
}])
assets/javascript/templates/map.html
<leaflet ng-hide="isVisible" defaults="defaults" markers="markers" layers="layers" height="100%" width="100%" maxbounds="maxbounds" ></leaflet>
assets/javascript/angular/app.js
var app = angular.module("litography", ['ngAnimate','ui.router','ngResource', 'templates', 'leaflet-directive', 'cn.offCanvas', 'ui.bootstrap', 'angular-flexslider'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider, $locationProvider) {
/**
* Routes and States
*/
$stateProvider
.state('home', {
url: "/",
views:{
"splash": {
templateUrl: "splash.html",
controller: "SplashCtrl"
},
"map":{
templateUrl: "map.html",
controller: "MapCtrl",
resolve:{
Markers: function(Markers){
return Markers
}
}
},
"menu":{
templateUrl: "menu.html",
controller: "MenuCtrl"
}
}
})
// default fall back route
$urlRouterProvider.otherwise('/');
// enable HTML5 Mode for SEO
$locationProvider.html5Mode(true);
}]);
It can happen if you forgot to add the file: leaflet.markercluster.js
Ok, from the reduced plunker it was easier to play around and find the bug.
You need to put overlays inside (i.e. as a property of) layers:
layers: {
baselayers: {
mapbox: {
name: 'mapbox',
url: 'http://api.tiles.mapbox.com/v4/{mapid}/{z}/{x}/{y}.png?access_token={apikey}',
type: 'xyz',
layerParams: {
apikey: 'pk.eyJ1IjoibGF1cmVuYmVuaWNob3UiLCJhIjoiQ1BlZGczRSJ9.EVMieITn7lHNi6Ato9wFwg',
mapid: 'laurenbenichou.jm96meb6',
name: "stories"
}
}
},
overlays: {
stories: {
type: 'group',
name: 'stories',
visible: true,
},
events: {
type: 'group',
name: 'events',
visible: false
}
}
}

Resources