Excanvas does not work in IE8 - internet-explorer-8

Excanvas does not work in IE8!!! I write program to paint by mouse on canvas element. There are fragment of my js-code
window.attachEvent('onload', function () {
function init() {
var w = document.getElementById('signatureImage').getAttribute('width');
var h = document.getElementById('signatureImage').getAttribute('height');
var removeSignatureImage = document.getElementById('signatureImage');
removeSignatureImage.parentNode.removeChild(removeSignatureImage);
var canvasDiv = document.getElementById('canvasDiv');
canvas = document.createElement('canvas');
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
canvas.setAttribute('style', 'border:1px solid #000000');
canvas.setAttribute('id', 'signatureImage');
canvasDiv.appendChild(canvas);
if (typeof G_vmlCanvasManager != 'undefined') {
canvas = window.G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d");
tool = new tool_pencil();
// Attach the mousedown, mousemove and mouseup event listeners.
var trackend = false;
var trackstart = false;
var trackmid = false;
canvas.onselectstart = function () {
canvas.onmousemove(); trackstart = true;
return false; }
canvas.onclick = function () { trackend = true; }
canvas.onmousemove = function () {
var mtarget = document.getElementById('signatureImage');
var x = event.clientX - canvas.offsetLeft;
var y = event.clientY - canvas.offsetTop;
var mtype = 'mousemove';
if (trackstart) {
trackstart = false;
trackmid = true;
mtype = 'mousedown';
}
if (trackend) {
trackmid = false;
mtype = 'mouseup';
}
if (trackend || trackmid || trackstart) {
trackend = false;
ev_canvas({
type: mtype,
_x: x,
_y: y,
target: mtarget
});
}
}
}
function tool_pencil() {
var tool = this;
this.started = false;
function getCoord(ev) {
var x = ev._x;
var y = ev._y;
if (tool.started == true) {
coords += x + "," + y + " ";
}
return [x, y];
}
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
context.stroke();
var coord = getCoord(ev);
}
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
coordList += coords + ";";
document.getElementById('coord').value = coordList;
coords = "";
}
};
}
When there is context.lineTo(ev._x, ev._y);
context.stroke(); - nothing happens! Although the coordinates are passed and canvas painted and initialized

Related

Fabric JS 2.4.1 How to use clipPath to crop image that has been scaled smaller or larger then 1:1 Ratio

I have been trying to figure out what I am doing wrong when calculation the position and size of the mask rectangle once the target image has been resized.
Below is the documentation from fabric JS:
clipPath :fabric.Object
a fabricObject that, without stroke define a clipping area with their shape. filled in black the clipPath object gets used when the object has rendered, and the context is placed in the center of the object cacheCanvas. If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center'
Type:
fabric.Object
Source:
fabric.js, line 12991
The code I have works perfectly when the image is not resized (scale 1:1 X & Y). In the code's function named rescaleMask I attempt to position the mask to a zero center X & Y and when I run my math manually on a piece of graph paper it appears the math is correct. Obviously there is a piece that I am unaware of that is causing the positioning to be off in different ways depending on the quadrant in which the crop is being performed. There is quite a bit of code here but it is important that the mask is created dynamically and not hard coded. The problem must be in the rescaleMask function so hopefully the rest of the code can be ignored.
I created a test image graph with numbered squares which I will crop by clicking the mask button, drawing a rectangle around one of the boxes with the mouse left button and then clicking the crop button. The problem occurs when you resize the image before creating the mask and cropping.
Here is the test image:
Here is a jsfiddle fabric Creating rect with a mouse dynamic js 2.4.1 sent as fix #4
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
<button id="mask">Mask</button>
<button id="crop">Crop</button>
JS
var lastSelectedPicture = null;
var isInsertingCropRectangle = false;
var canvas = new fabric.Canvas('c', {
selection: true,
preserveObjectStacking: true,
height: 700,
width: 800
});
var crop_rect, isDown, origX, origY, mask, target;
var done = false;
var src = "https://stealth-apsvaw.streamhoster.com/fabric_js_2_4_1_crop_test/graph_paper_540.png";
fabric.Image.fromURL(src, function(img) {
img.selectable = true;
img.id = 'target';
img.top = 30;
img.left = 30;
canvas.add(img);
});
canvas.on('object:added', function(e) {
target = null;
mask = null;
canvas.forEachObject(function(obj) {
//alert(obj.get('id'));
var id = obj.get('id');
if (id === 'target') {
target = obj;
canvas.setActiveObject(obj);
}
if (id === 'mask') {
//alert(done);
//alert('mask');
mask = obj;
}
});
});
canvas.on('object:modified', function(e) {
e.target.setCoords();
canvas.renderAll();
});
//////////////////////////////////////////////////////////
// MASK
//////////////////////////////////////////////////////////
document.getElementById("mask").addEventListener("click", function() {
isInsertingCropRectangle = true;
canvas.discardActiveObject();
lastSelectedPicture.selectable = false;
lastSelectedPicture.setCoords();
lastSelectedPicture.dirty = true;
canvas.renderAll();
canvas.discardActiveObject();
isInsertingCropRectangle = true;
});
//////////////////////////////////////////////////////////
// CROP
//////////////////////////////////////////////////////////
document.getElementById("crop").addEventListener("click", function() {
if (target !== null && mask !== null) {
target.setCoords();
// Re-scale mask
mask = rescaleMask(target, mask);
mask.setCoords();
// Do the crop
target.clipPath = mask;
target.dirty=true;
canvas.setActiveObject(target);
canvas.bringToFront(target);
target.selectable = true;
canvas.remove(mask);
canvas.renderAll();
console.log(target);
}
});
//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
// P R O B L E M I N T H I S F U N C T I O N
//////////////////////////////////////////////////////////
function rescaleMask(target, mask){
mask.scaleX = 1;
mask.scaleY = 1;
var targetCenterX = target.width * target.scaleX / 2;
var targetCenterY = target.height * target.scaleY / 2;
var maskOverlapX = mask.left - target.left;
var maskOverlapY = mask.top - target.top;
var centerBasedX = maskOverlapX - targetCenterX;
var centerBasedY = maskOverlapY - targetCenterY;
if( maskOverlapX >= targetCenterX){
centerBasedX = maskOverlapX - targetCenterX;
}
else{
centerBasedX = -(targetCenterX) + maskOverlapX;
}
if( maskOverlapY >= targetCenterY){
centerBasedY = maskOverlapY - targetCenterY;
}
else{
centerBasedY = -(targetCenterY) + maskOverlapY;
}
console.log('targetleft = '+target.left);
console.log('targettop = '+target.top);
console.log('targetCenterX = '+targetCenterX);
console.log('targetCenterY = '+targetCenterY);
console.log('maskleft = '+mask.left);
console.log('masktop = '+mask.top);
console.log('maskOverlapX = '+maskOverlapX);
console.log('maskOverlapY = '+maskOverlapY);
console.log('centerBasedX = '+centerBasedX);
console.log('centerBasedY = '+centerBasedY);
mask.left = centerBasedX;
mask.top = centerBasedY;
mask.originX = 'left';
mask.originY = 'top';
mask.setCoords();
mask.dirty=true;
canvas.renderAll();
//var newMask = mask;
return(mask);
}
canvas.on('mouse:down', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse down done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
crop_rect = new fabric.Rect({
left: origX,
top: origY,
width: pointer.x - origX,
height: pointer.y - origY,
opacity: .3,
transparentCorners: false,
selectable: true,
id: 'mask'
});
canvas.add(crop_rect);
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:move', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse move done = '+done);
if (done) {
canvas.renderAll();
return;
}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
crop_rect.set({
left: Math.abs(pointer.x)
});
}
if (origY > pointer.y) {
crop_rect.set({
top: Math.abs(pointer.y)
});
}
crop_rect.set({
width: Math.abs(origX - pointer.x)
});
crop_rect.set({
height: Math.abs(origY - pointer.y)
});
crop_rect.setCoords();
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:up', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse up done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = false;
crop_rect.set({
selectable: true
});
done = true;
}
else{
}
});
canvas.on('selection:created', function(event) {
console.log("canvas.on('selection:created'");
selectionChanged(event);
});
canvas.on('selection:updated', function(event) {
console.log("canvas.on('selection:updated'");
selectionChanged(event);
});
function selectionChanged(event){
console.log("selectionChanged");
console.log("selectionChanged type = "+event.target.type);
switch(event.target.type) {
case 'textbox':
break;
case 'image':
lastSelectedPicture = event.target;
break;
case 'rect':
break;
case 'group':
break;
default:
break;
}
}
You need to take in consideration the target.scaleX and target.scaleY for mask.
var lastSelectedPicture = null;
var isInsertingCropRectangle = false;
canvas = new fabric.Canvas('c', {
selection: true,
preserveObjectStacking: true,
height: 700,
width: 800
});
var crop_rect, isDown, origX, origY, mask, target;
var done = false;
var src = "https://stealth-apsvaw.streamhoster.com/fabric_js_2_4_1_crop_test/graph_paper_540.png";
fabric.Image.fromURL(src, function(img) {
img.selectable = true;
img.id = 'target';
img.top = 30;
img.left = 30;
canvas.add(img);
});
canvas.on('object:added', function(e) {
target = null;
mask = null;
canvas.forEachObject(function(obj) {
//alert(obj.get('id'));
var id = obj.get('id');
if (id === 'target') {
target = obj;
canvas.setActiveObject(obj);
}
if (id === 'mask') {
//alert(done);
//alert('mask');
mask = obj;
}
});
});
canvas.on('object:modified', function(e) {
e.target.setCoords();
canvas.renderAll();
});
//////////////////////////////////////////////////////////
// MASK
//////////////////////////////////////////////////////////
document.getElementById("mask").addEventListener("click", function() {
isInsertingCropRectangle = true;
canvas.discardActiveObject();
lastSelectedPicture.selectable = false;
lastSelectedPicture.setCoords();
lastSelectedPicture.dirty = true;
canvas.renderAll();
canvas.discardActiveObject();
isInsertingCropRectangle = true;
});
//////////////////////////////////////////////////////////
// CROP
//////////////////////////////////////////////////////////
document.getElementById("crop").addEventListener("click", function() {
if (target !== null && mask !== null) {
target.setCoords();
// Re-scale mask
mask = rescaleMask(target, mask);
mask.setCoords();
// Do the crop
target.clipPath = mask;
target.dirty=true;
canvas.setActiveObject(target);
canvas.bringToFront(target);
target.selectable = true;
canvas.remove(mask);
canvas.renderAll();
console.log(target);
}
});
//////////////////////////////////////////////////////////
// RE-SCALE MASK FOR CROPPING
// P R O B L E M I N T H I S F U N C T I O N
//////////////////////////////////////////////////////////
function rescaleMask(target, mask){
mask.scaleX = 1;
mask.scaleY = 1;
mask.scaleX/=target.scaleX;
mask.scaleY/=target.scaleY;
var targetCenterX = target.width * target.scaleX / 2;
var targetCenterY = target.height * target.scaleY / 2;
var maskOverlapX = mask.left - target.left;
var maskOverlapY = mask.top - target.top;
var centerBasedX = maskOverlapX - targetCenterX;
var centerBasedY = maskOverlapY - targetCenterY;
if( maskOverlapX >= targetCenterX){
centerBasedX = (maskOverlapX - targetCenterX)/target.scaleX;
}
else{
centerBasedX = (-(targetCenterX) + maskOverlapX)/target.scaleX;
}
if( maskOverlapY >= targetCenterY){
centerBasedY = (maskOverlapY - targetCenterY)/target.scaleY;
}
else{
centerBasedY = (-(targetCenterY) + maskOverlapY)/target.scaleY;
}
console.log('targetleft = '+target.left);
console.log('targettop = '+target.top);
console.log('targetCenterX = '+targetCenterX);
console.log('targetCenterY = '+targetCenterY);
console.log('maskleft = '+mask.left);
console.log('masktop = '+mask.top);
console.log('maskOverlapX = '+maskOverlapX);
console.log('maskOverlapY = '+maskOverlapY);
console.log('centerBasedX = '+centerBasedX);
console.log('centerBasedY = '+centerBasedY);
mask.left = centerBasedX;
mask.top = centerBasedY;
mask.originX = 'left';
mask.originY = 'top';
mask.setCoords();
mask.dirty=true;
canvas.renderAll();
//var newMask = mask;
return(mask);
}
canvas.on('mouse:down', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse down done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
crop_rect = new fabric.Rect({
left: origX,
top: origY,
width: pointer.x - origX,
height: pointer.y - origY,
opacity: .3,
transparentCorners: false,
selectable: true,
id: 'mask'
});
canvas.add(crop_rect);
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:move', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse move done = '+done);
if (done) {
canvas.renderAll();
return;
}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
crop_rect.set({
left: Math.abs(pointer.x)
});
}
if (origY > pointer.y) {
crop_rect.set({
top: Math.abs(pointer.y)
});
}
crop_rect.set({
width: Math.abs(origX - pointer.x)
});
crop_rect.set({
height: Math.abs(origY - pointer.y)
});
crop_rect.setCoords();
canvas.renderAll();
}
else{
}
});
canvas.on('mouse:up', function(o) {
if( isInsertingCropRectangle == true ){
console.log('mouse up done = '+done);
if (done) {
canvas.renderAll();
return;
}
isDown = false;
crop_rect.set({
selectable: true
});
done = true;
}
else{
}
});
canvas.on('selection:created', function(event) {
console.log("canvas.on('selection:created'");
selectionChanged(event);
});
canvas.on('selection:updated', function(event) {
console.log("canvas.on('selection:updated'");
selectionChanged(event);
});
function selectionChanged(event){
console.log("selectionChanged");
console.log("selectionChanged type = "+event.target.type);
switch(event.target.type) {
case 'textbox':
break;
case 'image':
lastSelectedPicture = event.target;
break;
case 'rect':
break;
case 'group':
break;
default:
break;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
<button id="mask">Mask</button>
<button id="crop">Crop</button>

How to use babel into appcelerator project?

I'm importend in my workspace, a project build in appcelerator.
So I have some error like this:
If I open any file, I have this situation:
I don't know, how to fix this.
So this is my plugin.js
"use strict";
exports.__esModule = true;
var _getIterator2 = require("babel-runtime/core-js/get-iterator");
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn");
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require("babel-runtime/helpers/inherits");
var _inherits3 = _interopRequireDefault(_inherits2);
var _optionManager = require("./file/options/option-manager");
var _optionManager2 = _interopRequireDefault(_optionManager);
var _babelMessages = require("babel-messages");
var messages = _interopRequireWildcard(_babelMessages);
var _store = require("../store");
var _store2 = _interopRequireDefault(_store);
var _babelTraverse = require("babel-traverse");
var _babelTraverse2 = _interopRequireDefault(_babelTraverse);
var _assign = require("lodash/assign");
var _assign2 = _interopRequireDefault(_assign);
var _clone = require("lodash/clone");
var _clone2 = _interopRequireDefault(_clone);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint max-len: 0 */
var GLOBAL_VISITOR_PROPS = ["enter", "exit"];
var Plugin = function (_Store) {
(0, _inherits3.default)(Plugin, _Store);
function Plugin(plugin, key) {
(0, _classCallCheck3.default)(this, Plugin);
var _this = (0, _possibleConstructorReturn3.default)(this, _Store.call(this));
_this.initialized = false;
_this.raw = (0, _assign2.default)({}, plugin);
_this.key = key;
_this.manipulateOptions = _this.take("manipulateOptions");
_this.post = _this.take("post");
_this.pre = _this.take("pre");
_this.visitor = _this.normaliseVisitor((0, _clone2.default)(_this.take("visitor")) || {});
return _this;
}
Plugin.prototype.take = function take(key) {
var val = this.raw[key];
delete this.raw[key];
return val;
};
Plugin.prototype.chain = function chain(target, key) {
if (!target[key]) return this[key];
if (!this[key]) return target[key];
var fns = [target[key], this[key]];
return function () {
var val = void 0;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
for (var _iterator = fns, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3.default)(_iterator);;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var fn = _ref;
if (fn) {
var ret = fn.apply(this, args);
if (ret != null) val = ret;
}
}
return val;
};
};
Plugin.prototype.maybeInherit = function maybeInherit(loc) {
var inherits = this.take("inherits");
if (!inherits) return;
inherits = _optionManager2.default.normalisePlugin(inherits, loc, "inherits");
this.manipulateOptions = this.chain(inherits, "manipulateOptions");
this.post = this.chain(inherits, "post");
this.pre = this.chain(inherits, "pre");
this.visitor = _babelTraverse2.default.visitors.merge([inherits.visitor, this.visitor]);
};
/**
* We lazy initialise parts of a plugin that rely on contextual information such as
* position on disk and how it was specified.
*/
Plugin.prototype.init = function init(loc, i) {
if (this.initialized) return;
this.initialized = true;
this.maybeInherit(loc);
for (var key in this.raw) {
throw new Error(messages.get("pluginInvalidProperty", loc, i, key));
}
};
Plugin.prototype.normaliseVisitor = function normaliseVisitor(visitor) {
for (var _iterator2 = GLOBAL_VISITOR_PROPS, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : (0, _getIterator3.default)(_iterator2);;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var key = _ref2;
if (visitor[key]) {
throw new Error("Plugins aren't allowed to specify catch-all enter/exit handlers. Please target individual nodes.");
}
}
_babelTraverse2.default.explode(visitor);
return visitor;
};
return Plugin;
}(_store2.default);
exports.default = Plugin;
module.exports = exports["default"];
How can I fixed it?
import syntax is from ECMAScript 6 - which is yet to be added in the Appcelerator Studio.
https://jira.appcelerator.org/browse/TISTUD-6927

nativescript: new object is not an new an empty opject

As example only.
I have:
var ListingModel = require("~/shared/view-models/listing-view-model");
function listing10(){
var Listing = new ListingModel.ListingViewModel();
Listing.horizontalAddData('10');
frameModule.topmost().navigate("/views/listing/list");
}
function listing20(){
var Listing = new ListingModel.ListingViewModel();
Listing.horizontalAddData('20');
frameModule.topmost().navigate("/views/listing/list");
}
function listing50(){
var Listing = new ListingModel.ListingViewModel();
Listing.horizontalAddData('50');
frameModule.topmost().navigate("/views/listing/list");
}
exports.listing10 = listing10;
exports.listing20 = listing20;
exports.listing50 = listing50;
When i now tap on event listing10 i receive all information which i require.
But, when i now tap event listing20 i become back the information from listing10 and listing20. That means, the the new object isnt a new object with cleared data?
Question: How i must call the new ListingModel.ListingViewModel(); so that i really become back a new, clear ListingViewModel Object?
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Model = require('~/shared/view-models/view-model');
var ListingViewModel = (function (_super) {
__extends(ListingViewModel, _super);
function ListingViewModel() {
_super.apply(this, arguments);
}
var params = {start: 0 , limit: 7};
var page = 0;
var _listRequested = new virtualArray.VirtualArray(7);
_listRequested.loadSize = params.limit;
var _fetch = new f.fetchHelper();
var loading = 0;
var that=this;
var indexes = [];
var horizontalindex = [];
var fetchlist = [];
var listing ='';
Object.defineProperty(ListingViewModel.prototype, "list", {
get: function () {
return _listRequested;
},
enumerable: true,
configurable: true
});
ListingViewModel.prototype.horizontalAddData = function(val) {
listing = val;
that.loadItems();
};
loadItems = function (args) {
if(loading == 1) return;
loading = 1;
fetchlist = [];
var url = config.apiUrl + '?t=listing'
+ '&listing=' + list
+ '&start=' + params.start
+ '&limit=' + params.limit;
_fetch.getJSON(url).then(function (res) {
try {
for (var i = 0; i < res.list.length; i++) {
var am = new Model.Model(res.list[i]);
fetchlist.push(am);
}
_listRequested.length = _listRequested.length + fetchlist.length;
_listRequested.load(params.start, fetchlist);
} catch (e) {
alert('error' + e);
}
params.start = params.start + params.limit;
loading = 0;
});
};
return ListingViewModel;
})(observable.Observable);
exports.ListingViewModel = ListingViewModel;

leaflet Dynamic icon with filter

I'm trying to create an map with the leaflet-simple-csv-master (i'm a beginner). It's really nice and i'd like to show different icons with my types (for exemple 1 icon for schools, one for administrations,...). I succed to show my different icons when i have the map but when i click to the filter, my icons disappear. In fact it doesn't work because it do not recognize my variable when i use the filter and i can read that my img is no more logos/type1.png but logos/undefined.png.
This is my all code.
var basemap = new L.TileLayer(baseUrl, {maxZoom: 17, attribution: baseAttribution, subdomains: subdomains, opacity: opacity});
var center = new L.LatLng(0, 0);
var map = new L.Map('map', {center: center, zoom: 2, maxZoom: maxZoom, layers: [basemap]});
var popupOpts = {
autoPanPadding: new L.Point(5, 50),
autoPan: true
};
var points = L.geoCsv (null, {
firstLineTitles: true,
fieldSeparator: fieldSeparator,
onEachFeature: function (feature, layer) {
var popup = '<div class="popup-content"><table class="table table-striped table-condensed">';
for (var clave in feature.properties) {
var title = points.getPropertyTitle(clave).strip();
var attr = feature.properties[clave];
if (title == labelColumn) {
layer.bindLabel(feature.properties[clave], {className: 'map-label'});
}
if (attr.indexOf('http') === 0) {
attr = '<a target="_blank" href="' + attr + '">'+ attr + '</a>';
}
if (attr) {
popup += '<tr><th>'+title+'</th><td>'+ attr +'</td></tr>';
}
}
popup += "</table></popup-content>";
layer.bindPopup(popup, popupOpts);
},
pointToLayer: function (feature, latlng) {
var logo = feature.properties['type'];
return L.marker(latlng, {
icon:L.icon({
iconUrl: 'logos/'+logo+'.png',
shadowUrl: 'marker-shadow.png',
iconSize: [25,41],
shadowSize: [41, 41],
shadowAnchor: [13, 20]
})
});
},
filter: function(feature, layer) {
total += 1;
if (!filterString) {
hits += 1;
return true;
}
var hit = false;
var lowerFilterString = filterString.toLowerCase().strip();
$.each(feature.properties, function(k, v) {
var value = v.toLowerCase();
if (value.indexOf(lowerFilterString) !== -1) {
hit = true;
hits += 1;
return false;
}
});
return hit;
}
});
var hits = 0;
var total = 0;
var filterString;
var markers = new L.MarkerClusterGroup();
var dataCsv;
var addCsvMarkers = function() {
hits = 0;
total = 0;
filterString = document.getElementById('filter-string').value;
if (filterString) {
$("#clear").fadeIn();
} else {
$("#clear").fadeOut();
}
map.removeLayer(markers);
points.clearLayers();
markers = new L.MarkerClusterGroup(clusterOptions);
points.addData(dataCsv);
markers.addLayer(points);
map.addLayer(markers);
try {
var bounds = markers.getBounds();
if (bounds) {
map.fitBounds(bounds);
}
} catch(err) {
// pass
}
if (total > 0) {
$('#search-results').html("Showing " + hits + " of " + total);
}
return false;
};
var typeAheadSource = [];
function ArrayToSet(a) {
var temp = {};
for (var i = 0; i < a.length; i++)
temp[a[i]] = true;
var r = [];
for (var k in temp)
r.push(k);
return r;
}
function populateTypeAhead(csv, delimiter) {
var lines = csv.split("\n");
for (var i = lines.length - 1; i >= 1; i--) {
var items = lines[i].split(delimiter);
for (var j = items.length - 1; j >= 0; j--) {
var item = items[j].strip();
item = item.replace(/"/g,'');
if (item.indexOf("http") !== 0 && isNaN(parseFloat(item))) {
typeAheadSource.push(item);
var words = item.split(/\W+/);
for (var k = words.length - 1; k >= 0; k--) {
typeAheadSource.push(words[k]);
}
}
}
}
}
if(typeof(String.prototype.strip) === "undefined") {
String.prototype.strip = function() {
return String(this).replace(/^\s+|\s+$/g, '');
};
}
map.addLayer(markers);
$(document).ready( function() {
$.ajax ({
type:'GET',
dataType:'text',
url: dataUrl,
contentType: "text/csv; charset=utf-8",
error: function() {
alert('Error retrieving csv file');
},
success: function(csv) {
dataCsv = csv;
populateTypeAhead(csv, fieldSeparator);
typeAheadSource = ArrayToSet(typeAheadSource);
$('#filter-string').typeahead({source: typeAheadSource});
addCsvMarkers();
}
});
$("#clear").click(function(evt){
evt.preventDefault();
$("#filter-string").val("").focus();
addCsvMarkers();
});
});

Make a draggable, rotating wheel snap to evenly distributed positions

I'm rendering a wheel in a WebGL canvas using mrdoob's THREE.js.
I want the wheel to
Spin around it's center
Be draggable by mouse or touch interaction
Slow down by applying fake friction
Snap to the center of a wedge whenever the rotation speed reaches a certain threshold.
You may think of the behaviour of the wheel as that of a lottery wheel.
So far I have achieved points 1-3. This is my code:
'use strict';
var WIDTH = 1080,
HEIGHT = 1080;
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
var scene = new THREE.Scene();
scene.add(camera);
camera.position.z = 300;
// Create renderer
var container = document.querySelector('#test');
var renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0x000000, 0);
container.appendChild(renderer.domElement);
// Create objects
var wheelMaterial = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('wheel.png'),
depthWrite: false,
alphaTest: 0.5
});
wheelMaterial.overdraw = true;
var wheel = new THREE.Mesh(
new THREE.PlaneGeometry(240, 240),
wheelMaterial);
scene.add(wheel);
// Mouse interaction
var isDragging = false;
var lastMouseCoords = null;
var mouseCoords = null;
container.addEventListener('mousedown', onDragStart, false);
container.addEventListener('touchstart', onDragStart, false);
container.addEventListener('mouseup', onDragEnd, false);
container.addEventListener('mouseout', onDragEnd, false);
container.addEventListener('touchend', onDragEnd, false);
container.addEventListener('mousemove', onMouseMove, false);
container.addEventListener('touchmove', onMouseMove, false);
function onDragStart(e) {
isDragging = true;
console.log('Dragging', e);
mouseCoords = pageCoordsToCanvasCoords(e);
rotationHistory = [];
}
function onDragEnd(e) {
isDragging = false;
lastMouseCoords = null;
mouseCoords = null;
console.log('Drag end');
}
function onMouseMove(e) {
e.preventDefault();
mouseCoords = pageCoordsToCanvasCoords(e);
}
// Utility functions
function pageCoordsToCanvasCoords(e) {
var canvasX;
var canvasY;
if ('touches' in e && e.touches.length > 0) {
canvasX = e.touches[0].pageX;
canvasY = e.touches[0].pageY;
} else {
canvasX = e.pageX
canvasY = e.pageY
}
canvasX -= e.target.offsetLeft;
canvasY -= e.target.offsetTop;
console.log(canvasX, canvasY);
return {
x: canvasX,
y: canvasY
};
}
function mouseCoordsToRotation(x, y) {
var origoX = WIDTH / 2.0,
origoY = HEIGHT / 2.0;
x = x - origoX;
y = y - origoY;
var atan = Math.atan2(x, y);
return atan;
}
function getMeanVelocity(history) {
if (history.length <= 1) {
return 0;
}
var movements = [];
var startTime = history[0].time;
var totalTimeElapsed = 0;
// Start from the second item in deltaRadians
for (var i = 1; i < history.length; i++) {
var item = history[i];
var movement = item.deltaRad;
movements.push(item.deltaRad);
var movementTimeDelta = item.time - startTime - totalTimeElapsed;
if (movementTimeDelta < 0) {
console.error('movementTimeDelta for history entry #' +
i + ' has travelled back in time somehow.');
}
totalTimeElapsed += movementTimeDelta;
}
var sum = movements.reduce(function (a, b) {
return a + b;
});
return sum / totalTimeElapsed;
}
function applyFakeFriction(velocity, time) {
/*
var currentRotation = wheel.rotation.z;
var nearestBorder = 0;
var nearestBorderDistance = 100;
for (var i = 0; i < PARTITIONS; i++) {
var partition = PARTITION_ARC * i - PARTITION_ARC * PARTITIONS / 2;
var distance = currentRotation - partition;
if (distance < 0) {
distance /= -1;
}
if (distance < nearestBorderDistance) {
console.log('distance is less than nearestBorderDistance')
nearestBorder = partition;
nearestBorderDistance = distance;
if (nearestBorderDistance < 0) {
nearestBorderDistance /= -1;
}
}
}
console.log('nearestBorderDistance: ', nearestBorderDistance);
*/
for (var i = 0; i < time; i++) {
velocity -= WHEEL_FRICTION; // * (nearestBorderDistance * BORDER_FRICTION);
}
return velocity;
}
var rotation = 1;
function snap() {
isSnapping = true;
/* Disabled, this the issue I'm asking about in the post
var update = function () {
cube.position.rotation = current.rotation;
}
var current = {
rotation: rotation
};
TWEEN.removeAll();
var easing = TWEEN.Easing['Elastic']['EaseInOut'];
var tweenHead = neww TWEEN.Tween(current)
.to({rotation: rotation})
.easing(easing)
.onUpdate(update);
tweenHead.start();
*/
}
var rotationHistory = []
var ROTATION_HISTORY_MAX_LENGTH = 5;
var WHEEL_FRICTION = 0.000001;
var BORDER_FRICTION = 2;
var PARTITIONS = 12;
var PARTITION_ARC = 1 * Math.PI / (PARTITIONS / 2); // The width of each section
var wheelVelocity = 0.1;
var wheelSlowDownVelocity = 0;
var lastWheelRotationTime;
var isSnapping = false;
// Render
function tick() {
// Rotate wheel
var currentTime = (new Date).getTime();
if (lastMouseCoords && isDragging) {
// Reset the velocity for the slowdown
wheelSlowDownVelocity = 0;
// Get the delta rotation since last mouse coordinates
var deltaRadians = mouseCoordsToRotation(mouseCoords.x, mouseCoords.y)
- mouseCoordsToRotation(lastMouseCoords.x, lastMouseCoords.y);
// Set the wheel rotation
wheel.rotation.z += deltaRadians;
// Save the rotation in the history and remove any excessive elements
rotationHistory.push({
time: currentTime,
deltaRad: deltaRadians
});
while (rotationHistory.length > ROTATION_HISTORY_MAX_LENGTH) {
rotationHistory.shift();
}
}
if (isDragging) {
lastMouseCoords = mouseCoords;
}
// Continue rotation of the released wheel
if (!isDragging && !lastMouseCoords && lastWheelRotationTime) {
var delta = currentTime - lastWheelRotationTime;
if (wheelSlowDownVelocity == 0) {
var meanVelocityOverTime = getMeanVelocity(rotationHistory);
wheelSlowDownVelocity = meanVelocityOverTime;
} else {
var currentIsNegative = wheelSlowDownVelocity < 0 ? true : false;
var currentVelocity = wheelSlowDownVelocity;
if (currentIsNegative) {
currentVelocity /= -1;
}
console.log('Current velocity: ', currentVelocity);
console.log('delta: ', delta);
var newVelocity = applyFakeFriction(currentVelocity,
delta);
console.log('New velocity: ', newVelocity);
if (newVelocity < 0) {
wheelSlowDownVelocity = 0;
rotationHistory = [];
} else {
if (currentIsNegative) {
// Revert to old polarity
newVelocity /= -1;
}
wheelSlowDownVelocity = newVelocity;
}
}
wheel.rotation.z += wheelSlowDownVelocity * delta;
}
while (wheel.rotation.z > 2 * Math.PI) {
console.log('Correcting rotation: ', wheel.rotation.z);
wheel.rotation.z -= 2 * Math.PI;
}
while (wheel.rotation.z < - (2 * Math.PI)) {
console.log('Correcting rotation: ', wheel.rotation.z);
wheel.rotation.z += 2 * Math.PI;
}
// Update the history record
lastWheelRotationTime = currentTime;
// Render scene and attach render callback to next animation frame.
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
}
tick();
I have the complete code, minus wheel.png over at https://gist.github.com/joar/5747498.
I have been searching for examples of this behaviour but this far I haven't found any.
Note to editors. Please do not change the tags of this post. tween.js != TweenJS.
I have solved the issue.
'use strict';
function Wheel (element, options) {
var self = this;
// Variable initialization
var WIDTH = options.width;
var HEIGHT = options.height;
if (!options.image) {
throw new Error('Image argument missing');
}
var image = options.image;
var showStats = options.showStats || options.stats;
// Core variables
var stats;
var wheel;
var domElement;
var scene;
var camera;
var renderer;
var rotationHistory;
var input;
var animate;
var run = false;
var ROTATION_HISTORY_MAX_LENGTH = 5;
switch (typeof element) {
case 'string':
domElement = document.querySelector(element);
break;
default:
if ('className' in element) {
domElement = element;
} else {
throw new Error('Invalid element: ', element);
}
}
if (typeof element == 'undefined') {
throw new Error('Invalid element.')
}
/* Initializes the WebGL canvas with the wheel plane */
function setupScene() {
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
scene = new THREE.Scene();
scene.add(camera);
camera.position.z = 300;
// Create renderer
var container = domElement;
renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH * 2, HEIGHT * 2);
renderer.setClearColor(0x000000, 0);
// Create objects
var wheelMaterial = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture(image),
depthWrite: false,
alphaTest: 0.5
});
wheelMaterial.overdraw = true;
wheel = new THREE.Mesh(
new THREE.PlaneGeometry(245, 245),
wheelMaterial);
scene.add(wheel);
container.appendChild(renderer.domElement);
}
function setupStats() {
// Init stats
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
}
function setup() {
setupScene();
if (showStats) {
setupStats();
}
}
setup();
// The tick function
function update() {
animate.update(); // Process interactions
self.renderer.render(self.scene, self.camera);
if (showStats) {
self.stats.update();
}
if (run) {
window.requestAnimationFrame(update);
}
}
animate = new Animate();
// Start and run the wheel every animationframe
function start() {
self.input = input = new Input(); // Start capturing input
run = true;
update();
}
/**
* Animate the wheel
*/
function Animate() {
var self = this;
self.velocity = 0;
var velocityPositive = 0;
self.friction = 0.001;
self.snapThreshold = 0.03;
self.isSnapping = false;
var lastAnimationTime;
self.tween;
var rotationHistory = [];
var PARTITIONS = 12;
var PARTITION_ARC = 1 * Math.PI / (PARTITIONS / 2); // The width of each section
function update() {
var currentTime = (new Date).getTime();
velocityPositive = self.velocity;
if (velocityPositive < 0) {
velocityPositive /= -1;
}
if (!self.isSnapping
&& !input.isDragging
&& velocityPositive < self.snapThreshold
&& velocityPositive > 0
&& lastAnimationTime) {
rotationHistory = [];
snap();
}
if (input.isDragging) {
self.isSnapping = false;
TWEEN.removeAll();
}
if (!self.isSnapping) {
/**
* If the mouse is dragging the wheel
*/
if (input.lastMouseCoords && input.isDragging && !input.isSnapping) {
// Reset the velocity for the slowdown
self.velocity = 0;
// Get the delta rotation since last mouse coordinates
var deltaRadians = input.mouseCoordsToRadian(
input.mouseCoords.x, input.mouseCoords.y)
- input.mouseCoordsToRadian(
input.lastMouseCoords.x,
input.lastMouseCoords.y);
// Set the wheel rotation
wheel.rotation.z += deltaRadians;
// Save the rotation in the history and remove any excessive elements
rotationHistory.push({
time: currentTime,
deltaRad: deltaRadians
});
while (rotationHistory.length > ROTATION_HISTORY_MAX_LENGTH) {
rotationHistory.shift();
}
}
if (input.isDragging) {
input.lastMouseCoords = input.mouseCoords;
}
// Continue rotation of the released wheel
if (!input.isDragging
&& !input.lastMouseCoords
&& lastAnimationTime
&& !self.isSnapping) {
var delta = currentTime - lastAnimationTime;
if (self.velocity == 0) {
var meanVelocityOverTime = getMeanVelocity(rotationHistory);
self.velocity = meanVelocityOverTime;
} else if (!self.isSnapping && !self.isDragging) {
var currentIsNegative = self.velocity < 0 ? true : false;
var currentVelocity = self.velocity;
if (currentIsNegative) {
currentVelocity /= -1;
}
var newVelocity = applyFakeFriction(currentVelocity,
delta);
if (newVelocity < 0) {
self.velocity = 0;
rotationHistory = [];
} else {
if (currentIsNegative) {
// Revert to old polarity
newVelocity /= -1;
}
self.velocity = newVelocity;
}
}
wheel.rotation.z += self.velocity * delta;
}
if (!self.isSnapping) {
while (wheel.rotation.z > 2 * Math.PI) {
wheel.rotation.z -= 2 * Math.PI;
}
while (wheel.rotation.z < - (2 * Math.PI)) {
wheel.rotation.z += 2 * Math.PI;
}
}
}
// Update snap tween
TWEEN.update();
// Update the history record
lastAnimationTime = currentTime;
}
function applyFakeFriction(velocity, time) {
/*
var currentRotation = wheel.rotation.z;
var nearestBorder = 0;
var nearestBorderDistance = 100;
for (var i = 0; i < PARTITIONS; i++) {
var partition = PARTITION_ARC * i - PARTITION_ARC * PARTITIONS / 2;
var distance = currentRotation - partition;
if (distance < 0) {
distance /= -1;
}
if (distance < nearestBorderDistance) {
console.log('distance is less than nearestBorderDistance')
nearestBorder = partition;
nearestBorderDistance = distance;
if (nearestBorderDistance < 0) {
nearestBorderDistance /= -1;
}
}
}
console.log('nearestBorderDistance: ', nearestBorderDistance);
*/
for (var i = 0; i < time; i++) {
velocity -= self.friction; // * (10000 * wheelSlowDownVelocityPositive); // * (nearestBorderDistance * BORDER_FRICTION);
}
return velocity;
}
function getNearestWedge() {
var currentRotation = wheel.rotation.z;
var nearestBorder = 0;
var nearestBorderDistance = 100;
for (var i = 0; i < PARTITIONS; i++) {
var partition = PARTITION_ARC * i - PARTITION_ARC * PARTITIONS / 2;
var distance = currentRotation - partition;
if (distance < 0) {
distance /= -1;
}
if (distance < nearestBorderDistance) {
console.log('distance is less than nearestBorderDistance')
nearestBorder = partition;
nearestBorderDistance = distance;
if (nearestBorderDistance < 0) {
nearestBorderDistance /= -1;
}
}
}
return {
position: nearestBorder,
distance: nearestBorderDistance
};
}
function snap() {
console.log('Snapping');
if (self.isSnapping) {
console.error('Already snapping, aborting.');
return;
}
self.isSnapping = true;
self.velocity = 0;
var nearest = getNearestWedge();
TWEEN.removeAll();
console.log('nearest: ', nearest.position, nearest.distance)
self.tween = new TWEEN.Tween({r: wheel.rotation.z})
.to({r: nearest.position})
.easing(TWEEN.Easing.Elastic.Out)
.onUpdate(onUpdate)
.onComplete(onComplete)
.start();
function onUpdate() {
//console.log('current: ', this.r, self.velocity);
wheel.rotation.z = this.r;
};
function onComplete() {
self.isSnapping = false;
console.log('Not snapping');;
}
}
function getMeanVelocity(history) {
if (history.length <= 1) {
return 0;
}
var movements = [];
var startTime = history[0].time;
var totalTimeElapsed = 0;
// Start from the second item in deltaRadians
for (var i = 1; i < history.length; i++) {
var item = history[i];
var movement = item.deltaRad;
movements.push(item.deltaRad);
var movementTimeDelta = item.time - startTime - totalTimeElapsed;
if (movementTimeDelta < 0) {
console.error('movementTimeDelta for history entry #' +
i + ' has travelled back in time somehow.');
}
totalTimeElapsed += movementTimeDelta;
}
var sum = movements.reduce(function (a, b) {
return a + b;
});
return sum / totalTimeElapsed;
}
// Internal utilities
function log() {
if (console && _log) {
var args = Array.prototype.slice.call(arguments, 0);
args.unshift('Animate: ')
console.log.apply(console, args);
}
}
// exports
this.update = update;
this.rotationHistory = rotationHistory;
this.PARTITIONS = PARTITIONS;
this.PARTITION_ARC = PARTITION_ARC;
this.snap = snap;
return this;
}
/**
* Handles input to the wheel.
*/
function Input() {
var self = this;
var _log = true;
domElement.addEventListener('mousedown', onDragStart, false);
//domElement.addEventListener('touchstart', onDragStart, false);
domElement.addEventListener('mouseup', onDragEnd, false);
domElement.addEventListener('mouseout', onDragEnd, false);
//domElement.addEventListener('touchend', onDragEnd, false);
domElement.addEventListener('mousemove', onMouseMove, false);
//domElement.addEventListener('touchmove', onMouseMove, false);
function onDragStart(e) {
self.isDragging = true;
log('Drag start');
self.mouseCoords = pageCoordsToCanvasCoords(e);
animate.rotationHistory = [];
}
function onDragEnd(e) {
self.isDragging = false;
self.lastMouseCoords = null;
self.mouseCoords = null;
log('Drag end');
}
function onMouseMove(e) {
e.preventDefault();
self.mouseCoords = pageCoordsToCanvasCoords(e);
}
function pageCoordsToCanvasCoords(e) {
var canvasX, canvasY;
if ('touches' in e && e.touches.length > 0) {
canvasX = e.touches[0].pageX;
canvasY = e.touches[0].pageY;
} else {
canvasX = e.pageX
canvasY = e.pageY
}
canvasX -= e.target.offsetLeft;
canvasY -= e.target.offsetTop;
// console.log(canvasX, canvasY);
return {
x: canvasX,
y: canvasY
};
}
function mouseCoordsToRadian(x, y) {
var origoX = WIDTH / 2.0,
origoY = HEIGHT / 2.0;
x = x - origoX;
y = y - origoY;
var atan = Math.atan2(x, y);
return atan;
}
// exports
this.mouseCoordsToRadian = mouseCoordsToRadian;
function log() {
if (console && _log) {
var args = Array.prototype.slice.call(arguments, 0);
args.unshift('Input: ')
console.log.apply(console, args);
}
}
return this;
}
// Internal utils
function log() {
if (console && _log) {
var args = Array.prototype.slice.call(arguments, 0);
args.unshift('Wheel: ')
console.log.apply(console, args);
}
}
// exports
self.start = start;
self.update = update;
self.scene = scene;
self.camera = camera;
self.wheel = wheel;
self.renderer = renderer;
self.stats = stats;
self.domElement = domElement;
self.input = input;
self.animate = animate;
return self;
}

Resources