Create Shape / Drag and Drop - event-handling

I'm trying to work with a createjs canvas to add new states (circles) on empty spots of stage on click, but drag existing points on click. I'm assuming the problem is that when I try to remove the stagemousemove event in handleMove it doesn't properly remove since it doesn't have a dedicated static handler, but I'm not sure how I'd even create that handler.
// create new state (circle)
function createState(e) {
x = e.stageX
y = e.stageY
if (stage.children.every(el =>
!(el.hitTest(x, y)) &&
!(el.hitTest(x + 30, y + 30)) &&
!(el.hitTest(x + 30, y - 30)) &&
!(el.hitTest(x - 30, y + 30)) &&
!(el.hitTest(x - 30, y - 30))
)) {
state = new createjs.Shape()
state.graphics.ss(3)
.s('#777')
.f('#eaeaea')
.dc(x, y, 30)
stage.addChild(state)
state.on('mousedown', handleMove)
stage.update()
}
}
// drag and drop
function handleMove(e) {
el = stage.getObjectUnderPoint(stage.mouseX, stage.mouseY);
var offset = {
x: el.x - e.stageX,
y: el.y - e.stageY
}
stage.on('stagemousemove', (evt) => {
el.x = offset.x + evt.stageX
el.y = offset.y + evt.stageY
stage.update()
})
stage.on('stagemouseup', (e) => {
stage.off('stagemousemove', (evt) => {
el.x = offset.x + evt.stageX
el.y = offset.y + evt.stageY
stage.update()
})
})
}
// determines whether to create new point
function handler(e) {
if (stage.getObjectUnderPoint(stage.mouseX, stage.mouseY) == null)
createState(e)
}
stage.on('stagemousedown', handler)

When you use the off() method, you must pass a reference to the result of the on() call.
let listener = stage.on('stagemousemove', (evt) => {
el.x = offset.x + evt.stageX
el.y = offset.y + evt.stageY
stage.update()
});
stage.on('stagemouseup', (e) => {
stage.off('stagemousemove', listener);
});
The on method creates a function closure for you automatically to maintain scope, which ES5 JavaScript does not do. This lets you pass a scope parameter to on so this is the expected scope.
When using arrow functions, this closure happens during ES5 transpiling. You can not just pass a similar function to the off, since they are not equal.
Hope that helps!

Related

How to subclass Polygon in Leaflet?

I am trying to create a Hex class for Leaflet where the center and side length should be in screen units (pixels) as opposed to lat/lng to obtain an effect like this over the map.
Code is here:
L.Hex = L.Polygon.extend({
initialize: function (args) {
console.log('arguments', JSON.stringify(arguments));
console.log('arguments[0]', JSON.stringify(arguments[0]));
// options = options || {};
this.center = args[0];
this.size = args[1];
this.options = args[2] || {};
L.Polygon.prototype.initialize.call(this, [], this.options);
},
points: function(center, size, map){
var latlngs = [0, 1, 2, 3, 4, 5, 6]
.map(p => this.hexCorner(center, size, p))
.map(p => map.layerPointToLatLng(p));
return latlngs;
},
hexCorner: function(center, size, i){
var angle_deg = 60 * i - 30;
var angle_rad = Math.PI / 180 * angle_deg;
return [center[0] + size * Math.cos(angle_rad),
center[1] + size * Math.sin(angle_rad)]
},
})
L.hex = function(){ return new L.Hex(arguments) }
Since the coordinates are in user space I think can only calculate them after adding to the map - but that's what fails. If add the points like this:
var h = L.hex([100, 100], 30);
var points = h.points([100, 100], 30, e.map);
h.setLatLngs(points);
h.addTo(map);
things work ok but my best attempt at onAdd which is this:
onAdd: function (map) {
var points = this.points(this.center, this.size, map);
this.setLatLngs(points);
map.addLayer(this);
},
with message TypeError: t._path is undefined
So the question is: where is the problem and how should I do it otherwise?
Change this.setLatLngs(points) to this._setLatLngs(points);.
With setLatLngs() the function this.redraw(); is called and this needs the map variable, which is applied with map.addLayer(this)

How do I leave the clicked point highlighted in dygraphs?

I am using the selected shapes to draw a larger diamond shape on my graph. When a user clicks a point. I display the data in another div, but I want to leave the clicked point highlighted. In other words, I want to 'toggle' data behind the points on and off and the clicked points need to show if they are included in the dataset. I believe I have seen this somewhere but I cannot find it. Is there a 'standard' way of leaving a clicked point in the 'highlight' state when you mouse away after clicking?
Here is my code. The pointClickCallback is getting the data through ajax and displaying it in another div. That part works. I just want to leave the point highlighted so I know which points I have clicked on.
I also need the point to revert back to normal when I click a second time. This is a toggle that allows me to select and unselect points.
EDIT: I found the interaction model example but when I add it to my code I lose my pointClickCallback functionality. I saw the call to captureCanvas and the interaction model structure.
var g = new Dygraph(document.getElementById('rothmangraph'), lines, {
//showRangeSelector: true,
title: "Personal Wellness Index (PWI)",
labels: ['Date', 'Index'],
color: ['#006699'],
valueRange: [0, 101],
axisLabelFontSize: 12,
drawPoints: true,
gridLineColor: "#aaaaaa",
includeZero: true,
strokeWidth: 2,
rightGap: 20,
pointSize: 4,
highlightCircleSize: 8,
series : {
Index: {
drawHighlightPointCallback : Dygraph.Circles.DIAMOND
},
},
axes: {
y: {
pixelsPerLabel: 20,
},
x: {
valueFormatter: function(ms) {
return ' ' + strftime('%m/%d/%Y %r',new Date(ms)) + ' ';
},
axisLabelWidth: 60,
axisLabelFormatter: function(d, gran) {
return strftime('%m/%d %I:%M %p',new Date(d.getTime())) ;
}
}
},
underlayCallback: function (canvas, area, g) {
var warning = g.toDomCoords(0,41);
var critical = g.toDomCoords(0,66);
// set background color
canvas.fillStyle = graphCol;
canvas.fillRect(area.x, area.y, area.w, area.h);
// critical threshold line
canvas.fillStyle = "#cc0000";
canvas.fillRect(area.x,warning[1],area.w,2);
// warning threshold line
canvas.fillStyle = "#cccc00";
canvas.fillRect(area.x,critical[1],area.w,2);
},
pointClickCallback: function(e,point) {
var idx = point.idx;
var line = lines[idx];
var sqltime = strftime('%Y-%m-%d %H:%M:%S',new Date(line[0]));
var dispdate = strftime('%m/%d %r',new Date(line[0]));
_secureAjax({
url: '/ajax/getDataPoint',
data: {'patient_id': pid, "rdate": sqltime},
success: function (result) {
// parse and add row to table if not exists.
var data = JSON.parse(result);
var aid = data['id'];
var indexCol = "#a9cced"
if (line[1] <= 65) indexCol = "#ede1b7";
if (line[1] <= 40) indexCol = "#e5bfcc";
var headerinfo = '<th class="'+aid+'"><span class="showindex" style="background-color:'+indexCol+'">'+line[1]+'</span></th>';
var fixdate = dispdate.replace(' ','<br>');
var headerdate = '<th class="'+aid+'">'+fixdate+'</th>';
// skip if already exists
var found = false;
var whichone = false;
$('#headerdate tr th').each(function(idx, item) {
if (fixdate == $(this).html()) {
found = true;
whichone = idx;
}
});
if (!found) {
$.each(data, function (idx, item) {
$('#' + idx).append('<td class="'+aid+'" style="width:70px">' + item + '</td>');
});
$('#headerdate tr').append(headerdate);
$('#headerinfo tr').append(headerinfo);
} else {
$('tr').each(function() {
$('.'+aid).remove();
});
}
}
});
}
});
}

What is wrong with my coffeescript translation of this JS?

Trying to improve my Coffeescript skills, so thought I'd covert this jcrop demo. However it's not working as expect. Specifically, the redraw function does not appear to be being called.
This works fine.
// Create a new Selection object extended from Selection
var CircleSel = function(){ };
// Set the custom selection's prototype object to be an instance
// of the built-in Selection object
CircleSel.prototype = new $.Jcrop.component.Selection();
// Then we can continue extending it
$.extend(CircleSel.prototype,{
zoomscale: 1,
attach: function(){
this.frame.css({
background: 'url(' + $('#target')[0].src.replace('750','750') + ')'
});
},
positionBg: function(b){
var midx = ( b.x + b.x2 ) / 2;
var midy = ( b.y + b.y2 ) / 2;
var ox = (-midx*this.zoomscale)+(b.w/2);
var oy = (-midy*this.zoomscale)+(b.h/2);
//this.frame.css({ backgroundPosition: ox+'px '+oy+'px' });
this.frame.css({ backgroundPosition: -(b.x+1)+'px '+(-b.y-1)+'px' });
},
redraw: function(b){
// Call original update() method first, with arguments
$.Jcrop.component.Selection.prototype.redraw.call(this,b);
this.positionBg(this.last);
return this;
},
prototype: $.Jcrop.component.Selection.prototype
});
But when I try to write this in Coffescript it fails
CircleSel.prototype = new ($.Jcrop.component.Selection)
$.extend CircleSel.prototype,
zoomscale: 1
attach: ->
#frame.css background: 'url(' + $('#target')[0].src.replace('750', '750') + ')'
return
positionBg: (b) ->
midx = (b.x + b.x2) / 2
midy = (b.y + b.y2) / 2
ox = -midx * #zoomscale + b.w / 2
oy = -midy * #zoomscale + b.h / 2
#frame.css backgroundPosition: -(b.x + 1) + 'px ' + -b.y - 1 + 'px'
return
# this redraw function is not being called, everything else appears to work fine
redraw: (b) ->
$.Jcrop.component.Selection::redraw.call this, b
#positionBg #last
this
prototype: $.Jcrop.component.Selection.prototype
What have I done wrong?

Removing items from leaflet markercluster

I have two functions that load markers into my map. (Both functions are called on success of an ajax call)
First Function is like this:
function loadEpsMarkers(data) {
for (var i = 0; i < data.length; i++) {
var plateNo = data[i].PLATE_NUMBER;
var permitNo = data[i].PERMITINFOID;
var inventoryId = data[i].INVENTORY_ID;
var icon = epsiconR;
if (data[i].INVENTORY_STATUS === 'Complete') {
icon = epsiconC;
}
var popup = '<h5>EPS</h5>' + 'Plate:' + plateNo + '<br/>' +
' Permit: <a class=\'link\' data-inventoryId="' + inventoryId + '" href=' + url + '>' + permitNo + '</a>' +
'<p style=\"color:blue\">' + '' + '<a class=\'link\' href=' + url + '>' +
'Import' + '</a>' + '<br/>' + '<a class=\'link\' href=' + url + '>' +
'Duplicate' + '</a>' + '<br/>' + '<a class=\'link\' href=' + url + '>' +
'Removed' + '</a>' + '<br/>' + '</p>';
var m = L.marker([data[i].REF_LATITUDE, data[i].REF_LONGITUDE], { icon: icon, draggable: 'true' })
.bindPopup(popup);
markerClusters.addLayer(m);
}
map.addLayer(markerClusters);
map.invalidateSize(false);
}
The second Function is same except the data is different.
This works fine and I get the clustered markers as expected.
Now I have a button, when I click this button, it should hide the markers from the first call.
The easy way out is to remove layers and then redo just the second call. But that seems like an inefficient way of doing this.
This gets more complex if I have 4 such data calls and I want to toggle markers from each of those calls.
I have tried something like this as well:
$('#dvEpsOnlyMarkers').click(function () {
markerClusters.removeLayer(m);
});
But that isn't working. Any ideas on how I can make this work ?
Thanks in advance
A very simple way of achieving what you describe is to store references to markers of each group in arrays, and manipulate those arrays to add / remove the layers into MCG:
var markersCall1 = [],
markersCall2 = [],
markersCall3 = [],
markersCall4 = [];
function loadEpsMarkersX(data) { // Replace X by 1...4
for (var i = 0; i < data.length; i++) {
// Convert data...
var m = L.marker(/* latLng, options, popup... */);
markersCallX.push(m); // Replace X by 1...4
}
// Actually add to MarkerClusterGroup.
// Replace X by 1...4
markerClusters.addLayers(markersCallX); // note the addLayers with "s".
}
$('#dvEpsOnlyMarkersX').click(function (event) { // Replace X by 1...4
// Assuming it is a checkbox, you can use it to toggle.
if (this.checked) {
// Replace X by 1...4
markerClusters.addLayers(markersCallX); // note the "s".
} else {
// Replace X by 1...4
markerClusters.removeLayers(markersCallX); // note the "s".
}
});
When you have batches of markers to add / remove like in your case, you can conveniently use the MarkerClusterGroup methods addLayers and removeLayers (with a trailing s) with an array of the markers to process. These methods are much more efficient than adding / removing markers one by one.

How to get "scrollTop"-value of FB.Canvas.getPageInfo

So far I can alert the "scrollTop"-value of "FB.Canvas.getPageInfo":
FB.Canvas.getPageInfo(
function(info) {
alert('scrollTop: ' + info.scrollTop);
}
);
Now I would like to use this value in one of my functions, but I don't know how.
The following is not working:
function test() {
var fbScrollTop = FB.Canvas.getPageInfo();
alert(fbScrollTop);
}
How can I access the the value?
I don't know if it's useful yet.
But now I can center my divs in my facebook app.
Here is the function:
jQuery.fn.center = function () {
var temp = this;
FB.Canvas.getPageInfo(
function(info) {
$(temp).css("position","absolute");
$(temp).css("top", Math.max(0, ((info.clientHeight - $(temp).outerHeight()) / 2) + info.scrollTop) + "px");
$(temp).css("left", Math.max(0, ((info.clientWidth - $(temp).outerWidth()) / 2) + info.scrollLeft) + "px");
}
);
return this;
}
And I use like that:
var loading = $("#loading");
loading.center();
Just combine both
function test() {
var fbScrollTop;
FB.Canvas.getPageInfo(function(info) {
fbScrollTop = info.scrollTop;
alert(fbScrollTop);
});
}
Most of the methods available in the SDK are asynchronous - this means that the response will not be available until the provided callback is invoked (with the response).